Essential SQLAlchemy - Rick Copeland
Essential SQLAlchemy - Rick Copeland
Table of Contents
A Note Regarding Supplemental Files
Preface
Audience
Assumptions This Book Makes
Contents of This Book
Conventions Used in This Book
Using Code Examples
How to Contact Us
Acknowledgments
1. Introduction to SQLAlchemy
What Is SQLAlchemy
The Object/Relational “Impedance Mismatch”
SQLAlchemy Philosophy
SQLAlchemy Architecture
Engine
MetaData Management
Types System
SQL Expression Language
Object Relational Mapper (ORM)
2. Getting Started
Installing SQLAlchemy
Installing the SQLAlchemy Package
Installing Some Database Drivers
SQLAlchemy Tutorial
Connecting to the Database and Creating Some Tables
Performing Queries and Updates
Mapping Objects to Tables
3. Engines and MetaData
Engines and Connectables
Configuring SQLAlchemy Logging
Database Connections and ResultProxys
Connection Pooling
MetaData
Getting Started with MetaData
Defining Tables
Column Definitions
Constraints
Defaults
Defining Indexes
Creating Explicit Sequences
MetaData Operations
4. SQLAlchemy Type Engines
Type System Overview
Built-in Types
Generic Types
Dialect-Specific Types
Application-Specific Custom Types
Implementing a TypeDecorator
Creating a New TypeEngine
5. Running Queries and Updates
Inserts, Updates, and Deletes
Insert Statements
Update Statements
Delete Statements
Queries
Basic Query Construction
Joins and Set Operations
Subqueries
6. Building an Object Mapper
Introduction to ORMs
Design Concepts in the ORM
Declaring Object Mappers
Basic Object Mapping
Customizing Property Mapping
Mapping Arbitrary Selectables
Other mapper() Parameters
Declaring Relationships Between Mappers
Basic Relationships
Using BackRefs
Using a Self-Referential Mapper
Cascading Changes to Related Objects
Other relation() and backref() Parameters
Extending Mappers
ORM Partitioning Strategies
Vertical Partitioning
Horizontal Partitioning
7. Querying and Updating at the ORM Level
The SQLAlchemy ORM Session Object
Creating a Session
Saving Objects to the Session
Updating Objects in the Session
Deleting Objects from the Session
Flushing, Committing, and Rolling Back Session
Changes
Other Session Methods
Extending Sessions
Querying at the ORM Level
ORM Querying with Joins
Customizing the Select Statement in ORM Queries
Other Query Methods
Contextual or Thread-Local Sessions
Using Contextual Sessions with Mappers and Classes
8. Inheritance Mapping
Overview of Inheritance Mapping
Single Table Inheritance Mapping
Concrete Table Inheritance Mapping
Joined Table Inheritance Mapping
Optimizing Performance with Joined Table Inheritance
Mapping
Relations and Inheritance
9. Elixir: A Declarative Extension to SQLAlchemy
Introduction to Elixir
Installing Elixir
Using Elixir
Fields and Properties
Relations
Inheritance
Querying Using Elixir
Elixir Extensions
Associable Extension
Encrypted Extension
Versioned Extension
10. SqlSoup: An Automatic Mapper for SQLAlchemy
Introduction to SqlSoup
Using SqlSoup for ORM-Style Queries and Updates
Joins with SqlSoup
Mapping Arbitrary Selectables
Directly Accessing the Session
Using SqlSoup for SQL-Level Inserts, Updates, and Deletes
When to Use SqlSoup Versus Elixir Versus “Bare”
SQLAlchemy
SqlSoup Pros and Cons
Elixir Pros and Cons
11. Other SQLAlchemy Extensions
Association Proxy
Ordering List
Deprecated Extensions
Index
Essential SQLAlchemy
Rick Copeland
Editor
Mary Treseler
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
Shows text that should be replaced with user-supplied values.
ALL CAPS
Shows SQL keywords and queries.
Note
Warning
This book is here to help you get your job done. In general, you may
use the code in this book in your programs and documentation. You
do not need to contact us for permission unless you’re reproducing a
significant portion of the code. For example, writing a program that
uses several chunks of code from this book does not require
permission. Selling or distributing a CD-ROM of examples from
O’Reilly books does require permission. Answering a question by
citing this book and quoting example code does not require
permission. Incorporating a significant amount of example code from
this book into your product’s documentation does require permission.
We appreciate, but do not require, attribution. An attribution usually
includes the title, author, publisher, and ISBN. For example: “Essential
SQLAlchemy by Rick Copeland. Copyright 2008 Richard D. Copeland,
Jr., 978-0-596-51614-7.”
If you feel your use of code examples falls outside fair use or the
permission given here, feel free to contact us at
[email protected].
How to Contact Us
Sebastopol, CA 95472
707-829-0104 (fax)
We have a web page for this book, where we list errata, examples,
and any additional information. You can access this page at:
http://www.oreilly.com/catalog/9780596516147
Although this code gets the job done, it is verbose, error-prone, and
tedious to write. Using string manipulation to build up a query as
done here can lead to various logical errors and vulnerabilities such
as opening your application up to SQL injection attacks. Generating
the string to be executed by your database server verbatim also ties
your code to the particular DB-API driver you are currently using,
making migration to a different database server difficult. For instance,
if we wished to migrate the previous example to the Oracle DB-API
driver, we would need to write:
sql="INSERT INTO user(user_name, password) VALUES (:1, :2)"
cursor = conn.cursor()
cursor.execute(sql, 'rick', 'parrot')
SQL injection is a type of programming error where carefully crafted user input can cause your application to execute arbitrary SQL code. For instance, suppose that the DB-
API code in the earlier listing had been written as follows:
sql="INSERT INTO user(user_name, password) VALUES ('%s', '%s')"
cursor = conn.cursor()
cursor.execute(sql % (user_name, password))
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.
To migrate this code to Oracle, you would write, well, exactly the
same thing.
SQLAlchemy also allows you to write SQL queries using a Pythonic
expression-builder. For instance, to retrieve all the users created in
2007, you would write:
statement = user_table.select(and_(
user_table.c.created >= date(2007,1,1),
user_table.c.created < date(2008,1,1))
result = statement.execute()
Notice that there is nothing particularly special about the User class
defined here. It is used to create “plain old Python objects,” or
POPOs. All the magic of SQLAlchemy is performed by the mapper.
Although the class definition just shown is empty, you may define
your own methods and attributes on a mapped class. The mapper
will create attributes corresponding to the column names in the
mapped table as well as some private attributes used by SQLAlchemy
internally. Once your table is mapped, you can use a Session object
to populate your objects based on data in the user table and flush
any changes you make to mapped objects to the database:
>>> Session = sessionmaker()
>>> session = Session()
>>>
>>> # Insert a user into the database
... u = User()
>>> u.user_name='rick'
>>> u.email_address='[email protected]'
>>> u.password='parrot'
>>> session.save(u)
>>>
>>> # Flush all changes to the session out to the database
... session.flush()
>>>
>>> query = session.query(User)
>>> # List all users
... list(query)
[<__main__.User object at 0x2abb96dae3d0>]
>>>
>>> # Get a particular user by primary key
... query.get(1)
<__main__.User object at 0x2abb96dae3d0>
>>>
>>> # Get a particular user by some other column
... query.get_by(user_name='rick')
<__main__.User object at 0x2abb96dae3d0>
>>>
>>> u = query.get_by(user_name='rick')
>>> u.password = 'foo'
>>> session.flush()
>>> query.get(1).password
'foo'
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
Now, our model plus the magic of the SQLAlchemy ORM allows us to
detect whether the given user is an administrator:
q = session.query(Permission)
rick_is_admin = q.count_by(permission_name='admin',
... user_name='rick')
Although the query is a little longer in this case, we are doing all of
the work in the database, allowing us to reduce the data transferred
and potentially increase performance substantially due to reduced
round-trips to the database. The important thing to note is that
SQLAlchemy makes “simple things simple, and complex things
possible.”
SQLAlchemy Philosophy
Using the object mapper pattern (where plain Python objects are
mapped to SQL tables via a mapper object, rather than requiring
persistent objects to be derived from some Persistable class)
achieves much of this separation of concerns. There has also been a
concerted effort in SQLAlchemy development to expose the full power
of SQL, should you wish to use it.
In SQLAlchemy, your objects are POPOs until you tell SQLAlchemy
about them. This means that it is entirely possible to “bolt on”
persistence to an existing object model by mapping the classes to
tables. For instance, consider an application that uses users, groups,
and permissions, as shown. You might prototype your application
with the following class definitions:
class User(object):
class Group(object):
class Permission(object):
Once your application moves beyond the prototype stage, you might
expect to have to write code to manually load objects from the
database or perhaps some other kind of persistent object store. On
the other hand, if you are using SQLAlchemy, you would just define
your tables:
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))
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))
Mapped classes are actually fairly unmolested by the default SQLAlchemy mapper. In particular, the mapped class is given the following new attributes:
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
One attribute will be added to the mapped class for each property specified in the mapper, as well as any “auto-mapped” properties, such as columns. In the previous
example, the mapper adds user_name, password, id, and _groups to the User class.
So, if you are planning on using SQLAlchemy, you should stay away from naming any class attributes c or _state, and you should be aware that SQLAlchemy will instrument
your class based on the properties defined by the mapper.
Most of the time, you will be using the higher-level facilities of the
SQL expression language and ORM components of SQLAlchemy, but
it’s nice to know that you can always easily drop down all the way to
raw SQL if you need to.
Connection pooling
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)
Once we have defined ImageType, we can use that type in our table
definitions, and the corresponding PIL image will be automatically
created when we select from the database or serialized when we
insert or update the database.
SQL Expression Language
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.
Most of the time, you will be using the SQL expression language by
creating expressions involving the attributes of the table.c object.
This is a special attribute that is added to Tables you have defined in
the metadata, as well as any objects you have mapped to tables or
other selectables. The “.c” objects represent database columns, and
they can be combined via a rich set of operators:
# Select all users with a username starting with 'r' who were
# created before June 1, 2007
q = user_table.select(
user_table.c.user_name.like('r%')
& user_table.c.created < datetime(2007,6,1))
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)
Object Relational Mapper (ORM)
Although you can do a lot with the Engine, Metadata, TypeEngine, and
SQL expression language, the true power of SQLAlchemy is found in
its ORM. SQLAlchemy’s ORM provides a convenient, unobtrusive way
to add database persistence to your Python objects without requiring
you to design your objects around the database, or the database
around the objects. To accomplish this, SQLAlchemy uses the data
mapper pattern. In this pattern, you can define your tables (or other
selectables, such as joins) in one module, your classes in another,
and the mappers between them in yet another module.
SQLAlchemy provides a great deal of flexibility in mapping tables, as
well as a sensible set of default mappings. Suppose that we defined
the following tables, classes, and mappers:
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))
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)
Here, the mapper would create properties on the User class for the
columns of the table: id, user_name, email_address, password,
first_name, last_name, and created. On the Group class, the id and
group_name properties would be defined. The mapper, however, has a
great deal more flexibility. If we wished to store only a hash of the
user’s password in the database, rather than the actual plaintext
password, we might modify the User class and mapper to the
following:
import sha
class User(object):
def _get_password(self):
return self._password
def _set_password(self, value):
self._password = sha.new(value).hexdigest()
password=property(_get_password, _set_password)
The ORM uses a Session object to keep track of objects loaded from
the database and the changes made to them. Sessions are used to
persist objects created by the application, and they provide a query
interface to retrieve objects from the database. Rather than
executing the database code to synchronize your objects with your
tables every time an object is modified, the Session simply tracks all
changes until its flush() method is called, at which point all the
changes are sent to the database in a single unit of work.
A Session class is created using the sessionmaker() function, and a
Session object is created by instantiating the class returned from
sessionmaker(). Although you can instantiate the Session object
directly, the sessionmaker function is a convenient way to fix the
parameters that will be passed to the Session’s constructor, rather
than repeating them wherever a Session is instantiated.
To insert objects into the database, we simply need to save them to
the session:
Session=sessionmaker()
session=Session()
u = User()
u.user_name='rick'
u.password='foo'
u.email_address='[email protected]'
session.save(u) # tell SQLAlchemy to track the object
session.flush() # actually perform the insert
You can even specify a “join chain” by using a list of properties for
the argument to join():
q = session.query(User)
# groups is a property of a User, permissions is a property of a
... Group
q = q.join(['groups', 'permissions'])
q = q.filter(Permission.c.permission_name=='admin')
users = list(q)
SetupTools includes a tool called easy_install, which can be used to install various Python modules from the CheeseShop. easy_install is particularly good at resolving
dependencies between Python packages and installing a package’s dependencies along with the package itself. If you intend to take advantage of the rich library of free
software available in the CheeseShop, or if you intend to take advantage of the benefits of distributing your own code through SetupTools, it is a good idea to become familiar
with all its features. You can find more documentation on SetupTools at http://peak.telecommunity.com/DevCenter/EasyInstall.
In Windows, you must make certain that you have administrator privileges before running easy_install or ez_setup.py, as both of these scripts modify your Python site-
packages directory.
In Windows, it’s also a good idea to make sure that Python and your Python scripts directories are on your path. In the default Python installation, these directories are
c:\python25 and c:\python25\scripts.
In Windows, you will need to open a command prompt and run the
bootstrap script as follows:
c:\>python ez_setup.py
Once you have installed SetupTools using ez_setup, you are ready to
install SQLAlchemy.
Installing SQLAlchemy with easy_install
Note
Python EGGs are typically distributed and installed as ZIP files. Although this is convenient for distribution, it is often nice to see the actual source code. easy_install includes
an option to specify that the EGG should be unzipped. The -UZ options as shown specify that SQLAlchemy should be Updated if already installed and should not be Zipped. If
you are installing SQLAlchemy for the first time, you can leave off the -U, and if you don’t care to look at the source code, you can leave off the -Z.
This book covers the 0.4 release of SQLAlchemy, so confirm that the
version installed on your system is at least 0.4.0.
SQLAlchemy also has an extensive unit test suite that can be
downloaded separately (not via easy_install) from
http://sqlalchemy.org if you wish to test the installation more
extensively.
Installing Some Database Drivers
For many of the examples in this book, we use the SQLite database
driver, mainly because it requires no separate database server
installation, and you can use it to generate throwaway in-memory
databases. Even if your production database is not SQLite, it can be
advantageous to install the driver for prototyping code and running
the examples in this book. The SQLite database driver became part of
the Python standard library in version 2.5, so if you are running more
recent versions of Python, you can skip this section.
Installing SQLite is different depending on whether you are using
Windows or another operating system. If you are using Windows, you
can download the pysqlite binary module from http://pysqlite.org/
and install it. If you are using another operating system, you will also
need to install the SQLite library from http://sqlite.org/.
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.
Connecting to the Database and Creating Some Tables
Notice how the Table constructor is given the SQL name of the table
('tf_user'), a reference to the metadata object, and a list of
columns. The columns are similarly defined with their SQL names,
data types, and various optional constraints. In this case, since we
defined an 'id' column as a primary key, SQLAlchemy will
automatically create the column with an auto-increment default
value. Also note that we can specify uniqueness and nullability
constraints on columns, provide literal defaults, or provide Python
callables (e.g., datetime.now) as defaults.
Next, we define our group and permission tables:
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))
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))
Once we have defined the tables in our schema, we can insert some
data. To create a new user, we use SQLAlchemy to construct an
INSERT statement using the following syntax:
stmt = user_table.insert()
Note again that SQLAlchemy uses bind parameters for the values to
be inserted, and that SQLAlchemy automatically generates the
created column value based on the result of calling datetime.now()
when the insert was executed.
To select data back out of the table, we can use the table’s select()
method as follows:
>>> stmt = user_table.select()
>>> result = stmt.execute()
>>> for row in result:
... print row
...
(1, u'rick', u'secret1', u'Rick Copeland',
... datetime.datetime(2007, 9, 7, 10, 6, 4, 415754))
(2, u'rick1', u'secret', u'Rick Copeland Clone',
... datetime.datetime(2007, 9, 7, 10, 6, 4, 476505))
(3, u'rick2', u'secret', u'Rick Copeland Clone 2',
... datetime.datetime(2007, 9, 7, 10, 6, 4, 543650))
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))]
To restrict the rows that are returned from the select() method, we
can supply a where clause. SQLAlchemy provides a powerful SQL
expression language to assist in the construction of where clauses, as
shown in the following example:
>>> stmt = user_table.select(user_table.c.user_name=='rick')
>>> print stmt.execute().fetchall()
[(1, u'rick', u'secret1', u'Rick Copeland',
... datetime.datetime(2007, 9, 7, 10, 6, 4, 415754))]
The simplest case of mapping is to just declare empty classes for our
application objects and declare an empty mapper:
class User(object): pass
class Group(object): pass
class Permission(object): pass
mapper(User, user_table)
mapper(Group, group_table)
mapper(Permission, permission_table)
Now that we have declared the mapping between our classes and
tables, we can start doing queries. First off, though, we need to
understand the unit of work (UOW) pattern. In UOW as implemented
by SQLAlchemy, there is an object known as a Session that tracks
changes to mapped objects and can flush() them out en masse to
the database in a single “unit of work.” This can lead to substantial
performance improvement when compared to executing multiple
separate updates. In SQLAlchemy, the Session class is created using
the sessionmaker() function, and the Session object is created by
instantiating the class returned from sessionmaker(). The intent is
that sessionmaker() should be called once (at the module level), with
its return value used to create individual sessions:
Session = sessionmaker()
session = Session()
We can also retrieve an object from the database by using its primary
key with the get() method on the Query object:
>>> query.get(1)
<__main__.User object at 0xb688d0>
Note the use of the .c attribute of the User object. It was added by
the mapper as a convenience to access the names of mapped
columns. If we wanted to, we could freely substitute
user_table.c.user_name for User.c.user_name, and vice versa.
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
If, however, we try to use the Query object, the ORM recognizes the
need to perform a flush() on the Session, inserts the new user, and
we get a count of 4:
>>> metadata.bind.echo = True
>>> query.count()
2007-09-09 21:33:09,482 INFO sqlalchemy.engine.base.Engine.0x..50
... INSERT INTO tf_user (user_name, password, display_name, created)
...
... VALUES (?, ?, ?, ?)
2007-09-09 21:33:09,482 INFO sqlalchemy.engine.base.Engine.0x..50
... ['mike', 'password', '', '2007-09-09 21:33:09.481854']
2007-09-09 21:33:09,485 INFO sqlalchemy.engine.base.Engine.0x..50
... SELECT count(tf_user.id)
FROM tf_user
2007-09-09 21:33:09,486 INFO sqlalchemy.engine.base.Engine.0x..50 []
4
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):
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
Note that you are never required to bind the MetaData object; all
operations that rely on a database connection can also be executed
by passing the Engine explicitly as the keyword parameter bind. This
is referred to as explicit execution. If a MetaData instance is bound,
then the bind parameter can be omitted from method calls that rely
on the database connection. This is referred to as implicit execution.
The “bound-ness” of a MetaData object is shared by all Tables,
Indexes, and Sequences in the MetaData, so a Table attached to a
bound MetaData, for instance, would be able to create itself via:
table.create()
whereas a Table in an unbound MetaData would need to supply a
bind parameter:
table.create(bind=some_engine_or_connection)
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']))
To actually create a table, you can call the create() method on it.
Here, we will create the style table on an in-memory SQLite database
and view the generated SQL:
>>> style_table.create(bind=create_engine('sqlite://', echo=True))
2007-08-25 08:05:44,396 INFO sqlalchemy.engine.base.Engine.0x..50
CREATE TABLE style (
brand_id INTEGER NOT NULL,
sku VARCHAR(80) NOT NULL,
code VARCHAR(80) NOT NULL,
PRIMARY KEY (brand_id, sku, code),
FOREIGN KEY(brand_id, sku) REFERENCES product (brand_id, sku)
)
We see that the composite primary key and foreign key constraints
are correctly generated. Although the foreign key constraints are
ignored by SQLite, it is still useful to generate them, as SQLAlchemy
can use this information to perform joins automatically based on the
foreign key relationships between tables.
The constructor Table.__init__(self,
Table name, metadata,*args,
**kwargs), takes the following arguments:
name
The table name as known by the database (may be combined
with the schema parameter).
metadata
The MetaData object to which to attach this table.
*args
The series of Column and Constraint objects to define for this
table.
schema
The schema name for this table, if required by the database. In
**kwargs, the default is None.
autoload
Indicates whether to reflect the columns from the database. In
**kwargs, the default is False.
autoload_with
The Connectable used to autoload the columns. In **kwargs, the
default is None.
include_columns
The list of column names (strings) to be reflected if
autoload=True. If None, all columns are reflected. If not None, any
columns omitted from the list will not be represented on the
reflected Table object. In **kwargs, the default is None.
mustexist
Indicates that the table must already be defined elsewhere in the
Python application (as part of this MetaData). An exception is
raised if this is not true. In **kwargs, the default is False.
useexisting
Directs SQLAlchemy to use the previous Table definition for this
table name if it exists elsewhere in the application. (SQLAlchemy
disregards the rest of the constructor arguments if this is True.)
in **kwargs, the default is False.
owner
Specifies the owning user of the table. This is useful for some
databases (such as Oracle) to help with table reflection. In
**kwargs, the default is None.
quote
Forces the table name to be escaped and quoted before being
sent to the database (useful for table names that conflict with
SQL keywords, for example). In **kwargs, the default is False.
quote_schema
Forces the schema name to be escaped and quoted before being
sent to the database. In **kwargs, the default is False.
The Table constructor also supports database-specific keyword
arguments. For instance, the MySQL driver supports the mysql_engine
argument to specify the backend database driver (i.e., 'InnoDB' or
'MyISAM', for instance).
Table reflection
You can also override the reflected columns if necessary. This can be
particularly useful when specifying custom column data types, or
when the database’s introspection facilities fail to identify certain
constraints.
brand_table = Table('brand', metadata,
Column('name', Unicode(255)), # override the reflected type
autoload=True)
You can also use the reflect() method of the MetaData to load the
schema. MetaData.reflect(bind=None, schema=None, only=None) takes
the following arguments:
bind
A Connectable used to access the database; required only when
the MetaData is unbound. The default is None.
schema
Specifies an alternate schema from which to reflect tables. The
default is None.
only
Directs the MetaData to load only a subset of the available tables.
This can be specified either as a sequence of the names to be
loaded or as a boolean callable that will be called for each
available table with the parameters only(metadata, table name).
If the callable returns True, the table will be reflected. The
default is None.
The constructor itself has the definition
MetaData
MetaData.__init__(bind=None, reflect=None).
Column Definitions
Primary keys
The constructor
ForeignKeyConstraint
ForeignKeyConstraint.__init__(self, columns, refcolumns, name=None,
onupdate=None, ondelete=None, use_alter=False) takes the same
parameters as the ForeignKey constructor except for columns and
refcolumns:
columns
Either a list of Column objects or a list of database-recognized
strings (such as tablename.columnname or
schemaname.tablename.columnname) that specifies the referenced
column in the local table (the compound foreign key)
refcolumns
Either a list of Column objects or a list of database-recognized
strings (such as tablename.columnname or
schemaname.tablename.columnname) that specifies the referenced
column in the remote table (the compound primary key)
UNIQUE constraints
UniqueConstraint is a more flexible version of specifying unique=True
in the Column definition, as it allows multiple columns to participate in
a uniqueness constraint:
product_table = Table(
'product', metadata,
Column('id', Integer, primary_key=True),
Column('brand_id', Integer, ForeignKey('brand.id')),
Column('sku', Unicode(80)),
UniqueConstraint('brand_id', 'sku'))
CHECK constraints
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))
PostgreSQL does not support passive defaults for primary keys. This is due to the fact that SQLAlchemy does not use the PostgreSQL OIDs to determine the identity of rows
inserted (OIDs are actually disabled by default in PostgreSQL version 8.), and psycopg2’s cursor.lastrowid() function only returns OIDs. Thus, the only way to know the
primary key of a row that is being inserted is to provide it as an active default.
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))
If the index is defined before the table is created, then the index will
be created along with the table. Otherwise, you can create the index
independently via its own create() function:
>>> i = Index('idx_name', user_table.c.first_name,
... user_table.c.last_name,
... unique=True)
>>> i.create(bind=e)
2007-08-25 16:30:36,566 INFO sqlalchemy.engine.base.Engine.0x..90
... CREATE UNIQUE INDEX idx_name ON tf_user (first_name, last_name)
2007-08-25 16:30:36,566 INFO sqlalchemy.engine.base.Engine.0x..90
... None
2007-08-25 16:30:36,567 INFO sqlalchemy.engine.base.Engine.0x..90
... COMMIT
Index("idx_name", Column('first_name', Unicode(length=255),
... default=ColumnDefault('')),
... Column('last_name',Unicode(length=255),
... default=ColumnDefault('')), unique=True)
Creating Explicit Sequences
Binding MetaData
Binding the MetaData object to an engine allows the MetaData and the
objects attached to it (Tables, Indexes, Sequences, etc.) to perform
database operations without explicitly specifying an Engine:
from sqlalchemy import *
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))
Bound and unbound MetaData objects can create and drop schema
objects either by using the create() and drop() methods on the
objects, or by using the MetaData methods create_all() and
drop_all(). The schema objects’ (Table, Index, and Sequence)
create() and drop() and methods take the following keyword
parameters:
bind
The Engine on which to execute the schema item creation
(default is None).
checkfirst
Add an IF NOT EXISTS or IF EXISTS clause, whichever is
appropriate to the SQL generated (not supported for Indexes).
The default is False.
The MetaData object itself supports the following arguments to its
create_all() and drop_all methods:
bind
The Engine on which to execute the operation. The default is
None.
tables
The Table objects to create/drop. If not specified, create/drop all
schema items known to the MetaData. The default is None.
checkfirst
Add an IF NOT EXISTS or IF EXISTS clause (whichever is
appropriate to the SQL generated). The default is False.
A table that has been created against one MetaData can be adapted
to another MetaData via the Table.tometadata (self, metadata,
schema=None) method. This can be useful when working with the
same schema in more than one Engine because it allows you to have
bound MetaData and Tables for both engines. You can also use the
MetaData. table_iterator() method to reflect an entire schema into
another engine, for example:
meta1 = MetaData('postgres://postgres:password@localhost/test',
... reflect=True)
meta2 = MetaData('sqlite://')
for table in meta1.table_iterator():
table.tometadata(meta2)
meta2.create_all()
Chapter 4. SQLAlchemy Type Engines
This chapter introduces the SQLAlchemy type system. It covers the
built-in types provided by SQLAlchemy: database-independent types
and database-specific types. It then tells you how to create your own
custom types for use in mapping application data onto your database
schema.
Type System Overview
If you want to keep your application portable across database servers, it is a good idea to stick to the generic types and (possibly) application-specific custom types, as any
code that relies on database dialect-specific TypeEngines will need to be modified if the database changes. In the SQLAlchemy tradition of not getting in your way, however,
full support is provided for dialect-specific TypeEngines if you wish to exploit database server-specific types.
Generic Types
length (default is
String string TEXT or VARCHAR
unbounded)
length (default is
Binary byte string BLOB
unbounded)
length (default is
Unicode unicode TEXT or VARCHAR
unbounded)
length (default is
TEXT(String) string TEXT
unbounded)
INT,
int INTEGER none
INTEGER(Integer)
length (default is
CLOB(String) string TEXT
unbounded)
length (default is
VARCHAR(String) string VARCHAR or TEXT
unbounded)
length (default is
CHAR(String) string CHAR or TEXT
unbounded)
length (default is
BLOB(Binary) byte string BLOB
unbounded)
class MyCustomEnum(types.TypeDecorator):
impl=types.Integer
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]
Creating a New TypeEngine
def get_col_spec(self):
return 'NEWTYPE(%s)' % ','.join(self._args)
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>
Note that prior to execution, the SQL has a bind parameter for
the id column, but when the statement is executed, id is
omitted because no value was provided for it.
Correlated update statements can also be generated using the SQL
expression language. A correlated update is an update whose values
are provided by a select statement. Suppose that we have a product
catalog with the schema in the following listing, and the data in
Tables 5-1 through 5-3:
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))
"123" 12.34
"456" 22.12
"789" 41.44
1 "Main Store"
2 "Secondary Store"
"123" 1 0
"456" 1 0
"789" 1 0
"123" 2 0
"456" 2 0
"789" 2 0
If we wish to globally set the price for all products in all stores to
their MSRP price, we could execute the following update:
>>> msrp = select(
... [product_table.c.msrp],
... product_table.c.sku==product_price_table.c.sku,
... limit=1)
>>> stmt = product_price_table.update(
... values=dict(price=msrp))
>>> stmt.execute()
2007-09-26 10:05:17,184 INFO sqlalchemy.engine.base.Engine.0x..d0
... UPDATE product_price SET price=(SELECT product.msrp
FROM product
WHERE product.sku = product_price.sku
LIMIT 1 OFFSET 0)
2007-09-26 10:05:17,184 INFO sqlalchemy.engine.base.Engine.0x..d0
... []
2007-09-26 10:05:17,185 INFO sqlalchemy.engine.base.Engine.0x..d0
... COMMIT
<sqlalchemy.engine.base.ResultProxy object at 0xd0e510>
"123" 1 12.34
"456" 1 22.12
"789" 1 41.44
"123" 2 12.34
"456" 2 22.12
"789" 2 41.44
Delete Statements
To select all columns from the product_table, you would use the
Table.select() method:
>>> stmt = product_table.select()
>>> for row in stmt.execute():
... print row
...
(u'123', Decimal("12.34"))
(u'456', Decimal("22.12"))
(u'789', Decimal("41.44"))
The actual parameters used by select() are listed next. They are
discussed in more detail later in the chapter.
columns=None
A list of ClauseElement structures to be returned from the query.
bind=None
An engine on a connectable object on which to execute the
statement. If this is omitted, an engine binding will be inferred
from referenced columns and/or tables, if possible.
whereclause=None
A ClauseElement expression used to for the WHERE clause.
from_obj=[]
A list of Tables or other selectable objects that will be used to
form the FROM clause. If this is not specified, the FROM clause is
inferred from the tables referenced in other clauses.
order_by=None
A list of ClauseElements used to construct the ORDER BY clause.
group_by=None
A list of ClauseElements used to construct the GROUP BY clause.
having=None
A ClauseElement used to construct the HAVING clause.
distinct=False
Adds a DISTINCT qualifier to the column list in the SELECT
statement.
for_update=False
Adds a FOR UPDATE qualifier to the SELECT statement. Some
databases support other values for this parameter, such as
MySQL, which supports "read" (translating to LOCK IN SHARE
MODE), or Oracle, which supports "nowait" (translating to FOR
UPDATE NOWAIT).
limit=None
The numerical limit for the number of rows returned. Typically
this uses the LIMIT clause, but SQLAlchemy provides some
support for LIMIT even when the underlying database does not
support it directly.
offset=None
The numerical offset for the starting row that is returned.
Typically this uses the OFFSET clause, but SQLAlchemy provides
some support for OFFSET even when the underlying database
does not support it directly.
correlate=True
Indicates that this SELECT statement is to be “correlated” with
its enclosing SELECT statement if it is used as a subquery. In
particular, any selectables present in both this statement’s
from_obj list and the enclosing statement’s from_obj list will be
omitted from this statement’s FROM clause.
use_labels=False
Generates unique labels for each column in the columns list, to
ensure there are no name collisions.
prefixes=None
A list of ClauseElements to be included directly after the SELECT
keyword in the generated SQL. This is used for dialect-specific
SQL extensions, to insert text between the SELECT keyword and
the column list.
Thus far, we have glossed over the return value of the execute()
method on SQL statements, showing only that it is possible to iterate
over this value and receive tuple-like objects. In fact, SQLAlchemy
provides an object, defined in the ResultProxy class, to allow cursor-
like access to the results of a query. Some of the useful methods and
attributes available on the ResultProxy object are summarized here:
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.
The “rows” returned from a ResultProxy object, either via the fetch*
() methods or iteration, is actually a RowProxy object. As we have
seen previously, it supports a tuple-like interface. We can also
retrieve columns from the RowProxy object through its dict-like
interface or its object-like interface:
>>> result = select([product_table]).execute()
>>> row = result.fetchone()
>>> print row
(u'123', 12.34)
>>> print row[0]
123
>>> print row['sku']
123
>>> print row[product_table.c.sku]
123
>>> print row.sku
123
>>> print row.items()
[('sku', u'123'), ('msrp', 12.34)]
>>> print row.keys()
['sku', 'msrp']
>>> print row.values()
[u'123', 12.34]
>>> print row.has_key('msrp')
True
>>> print row.has_key('price')
False
Operators and functions in WHERE clauses
SQLAlchemy also provides for use of the SQL boolean operators AND,
OR, and NOT, as well as the LIKE operator for comparing strings. The
bitwise logical operators &, |, and ~ are used to implement AND, OR,
and NOT, while the like() method on ClauseElements is used to
implement LIKE. Special care must be taken when using the AND,
OR, and NOT overloads because of Python operator precendence
rules. For instance, & binds more closely than <, so when you write A
< B & C < D, what you are actually writing is A < (B&C) < D, which
is probably not what you intended. You can also use the
SQLAlchemy-provided functions and_, or_, and not_ to represent
AND, OR, and NOT if you prefer:
>>> print (product_table.c.msrp > 10.00) & (product_table.c.msrp <
... 20.00)
product.msrp > ? AND product.msrp < ?
>>> print and_(product_table.c.msrp > 10.00,
... product_table.c.msrp < 20.00)
product.msrp > ? AND product.msrp < ?
>>> print product_table.c.sku.like('12%')
product.sku LIKE ?
>>> print ~((product_table.c.msrp > 10.00) &
... (product_table.c.msrp < 20.00))
NOT (product.msrp > ? AND product.msrp < ?)
>>> print not_(and_(product_table.c.msrp > 10.00,
... product_table.c.msrp < 20.00))
NOT (product.msrp > ? AND product.msrp < ?)
SQLAlchemy also provides for the use of arbitrary SQL functions via
the func variable, which generates functions using attribute access.
You can also use the special function func._ to add parentheses
around a subexpression if necessary:
>>> print func.now()
now()
>>> print func.current_timestamp
current_timestamp
>>> print func.abs(product_table.c.msrp)
abs(product.msrp)
>>> print func._(text('a=b'))
(a=b)
We can use bind parameters with text() by using the “named colon”
format (:name) for the bind parameters. We can also bind the clause
constructed to a particular engine using the bind parameter to the
text() function.
>>> stmt = text("SELECT product.msrp FROM product WHERE
... product.sku==:sku",
... bind=metadata.bind)
>>> print stmt
SELECT product.msrp FROM product WHERE product.sku==?
>>> print stmt.compile().get_params()
ClauseParameters:{'sku': None}
>>> print stmt.execute(sku='456').fetchall()
[(22.120000000000001,)]
Both the WHERE clause in SQL and the HAVING clause restrict results to those results matching a given SQL expression. The difference is that HAVING is always accompanied
by grouping (typically via the GROUP BY clause), and the HAVING clause filters the results after they are grouped, whereas the WHERE clause filters the rows before they are
grouped. WHERE clauses therefore can’t reference the results of aggregation functions such as SUM or COUNT, but the HAVING clause can.
One common operation when working with large data sets is the use
of the OFFSET and LIMIT clauses to return only a subset of data from
a cursor. SQLAlchemy supports OFFSET and LIMIT (even in databases
without direct support) through the use of offset and limit with the
select() function and method:
>>> stmt = product_table.select()
>>> print stmt.execute().fetchall()
[(u'123', 12.34), (u'456', 22.120000000000001), (u'789',
... 41.439999999999998)]
>>> stmt = product_table.select(offset=1, limit=1)
>>> print stmt
SELECT product.sku, product.msrp
FROM product
LIMIT 1 OFFSET 1
>>> print stmt.execute().fetchall()
[(u'456', 22.120000000000001)]
Up until this point, we have been using the select() function and
method as a query constructor, generating a complete SQL statement
as a result of the select() call. SQLAlchemy also supports a
“generative” interface for the select() function and method that
allows us to build up the query, one piece at a time. For instance,
suppose we have a product table with the following defintion:
product_table = Table(
'product', metadata,
Column('id', Integer, primary_key=True),
Column('sku', String(20), unique=True),
Column('manufacturer', Unicode(255)),
Column('department', Unicode(255)),
Column('category', Unicode(255)),
Column('class', Unicode(255)),
Column('subclass', Unicode(255)))
Now, suppose we have a user interface that displays all the “product”
records in the system, optionally filtered by various criteria
(manufacturer, department, etc.). We might write the following
function to return the filtered user list:
def get_prods(manufacturer=None,
department=None,
category=None,
class_=None,
subclass=None,
offset=None,
limit=None):
where_parts = []
if manufacturer is not None:
where_parts.append(product_table.c.manufacturer
== manufacturer)
if department is not None:
where_parts.append(product_table.c.department
== department)
if category is not None:
where_parts.append(product_table.c.category
== category)
if class_ is not None:
where_parts.append(product_table.c.class_
== class_)
if subclass is not None:
where_parts.append(product_table.c.subclass
== subclass)
whereclause=and_(*where_parts)
query = product_table.select(whereclause,
offset=offset, limit=limit)
return query
We can use arbitrary filters, and the appropriate SQL WHERE clause
will automatically be constructed for us automatically:
>>> q = get_prods()
>>> print q
SELECT product.id, product.sku, product.manufacturer,
... product.department, product.category, product.class,
... product.subclass
FROM product
>>> q = get_prods(manufacturer="Neon")
>>> print q
SELECT product.id, product.sku, product.manufacturer,
... product.department, product.category, product.class,
... product.subclass
FROM product
WHERE product.manufacturer = ?
>>> q = get_prods(manufacturer="Neon", department="Auto")
>>> print q
SELECT product.id, product.sku, product.manufacturer,
... product.department, product.category, product.class,
... product.subclass
FROM product
WHERE product.manufacturer = ? AND product.department = ?
Although the two functions have the same functionality, the second
one (using the generative interface) is more flexible. Suppose we
wanted to refactor the original function into multiple parts, with each
part potentially adding a different filtering criterion. In that case, we
would need to pass a where_parts list through all the intermediate
functions. In the generative approach, all the information about the
query is “wrapped up” in the query itself, allowing us to build up a
query piecemeal in several different functions, without passing
anything around but the query itself.
The generative interface actually consists of a set of methods on the
statement constructed by the select() function or method. Those
methods are summarized next. Note that none of these functions
actually modify the query object in place; rather, they return a new
query object with the new condition applied:
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.
Joins and Set Operations
Joining selectables
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
In some cases, we are not using the JOINed table to filter results, but
we would like to see the results from a JOINed table alongside results
from the table we are using. In this case, we can either use the
select() function or use the column() method of the query object:
>>> print query.column('product.sku')
SELECT store.id, store.name, product.sku
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
>>> query2 = select([store_table, product_table.c.sku],
... from_obj=[from_obj],
... whereclause=(product_table.c.msrp
... !=product_price_table.c.price))
>>> print query2
SELECT store.id, store.name, product.sku
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
But what if we want to return results that may not have matching
rows in the JOINed table? For this, we use the outerjoin
function/method:
>>> from_obj = store_table.outerjoin(product_price_table)
>>> from_obj = from_obj.outerjoin(product_table)
>>> query = store_table.select()
>>> query = query.select_from(from_obj)
>>> query = query.column('product.msrp')
>>> print query
SELECT store.id, store.name, product.msrp
FROM store LEFT OUTER JOIN product_price
... ON store.id = product_price.store_id
LEFT OUTER JOIN product
... ON product.sku = product_price.sku
Using aliases
When using joins, it is often necessary to refer to a table more than
once. In SQL, this is accomplished by using aliases in the query. For
instance, suppose we have the following (partial) schema that tracks
the reporting structure within an organization:
employee_table = Table(
'employee', metadata,
Column('id', Integer, primary_key=True),
Column('manager', None, ForeignKey('employee.id')),
Column('name', String(255)))
There are two major patterns used in the ORM you should become
familiar with in order to understand how to best use the ORM. These
are the data mapper pattern and the unit of work pattern.
The major second pattern used in the SQLAlchemy ORM is the unit of
work pattern. In this pattern, when you make a change to an object,
the database is not updated immediately. Instead, SQLAlchemy tracks
changes to your objects in a session object, and then flushes all your
changes at once in a single “unit of work.” This has the advantage of
generally improving performance by reducing the number of round-
trips to the database.
The alternative to the unit of work pattern, of course, is to update
the database as soon as a mapped object property changes. This can
lead to a very “chatty” application, but it does have the advantage of
keeping your objects in sync with the database, which can be handy
if you wish to execute queries before flushing the objects you’ve
modified back out to the database.
To alleviate this concern, SQLAlchemy actually provides an
“autoflush” feature on the session object that will take care of
flushing the session before any queries are performed on it. As long
as you use an autoflushing session and execute all queries through
the session, you generally do not need to worry about
inconsistencies between your objects in memory and the database on
disk.
Warning
Of course, if you use the SQL expression layer of SQLAlchemy, you can get your in-memory objects out-of-sync with the database, so some care needs to be taken when
mixing ORM-level semantics with SQL-level semantics in the same transaction.
Declaring Object Mappers
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.
The application object model in the following listing is extremely
basic. In a real application, the classes would probably have
additional methods defined for performing domain-specific
operations:
class Level(object):
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)
Basic Object Mapping
Now that we have the basic schema and object model in place, we
can start exploring how to map objects. The region_table is one of
the simplest tables, so we will start there. The following example
demonstrates mapping the region_table to the Region class, and also
illustrates the alterations that SQLAlchemy performs on the Region
class during mapping:
>>> print dir(Region)
['__class__', '__delattr__', '__dict__', '__doc__',
... '__getattribute__', '__hash__', '__init__',
... '__module__','__new__', '__reduce__', '__reduce_ex__',
... '__repr__', '__setattr__', '__str__', '__weakref__']
>>> mapper(Region, region_table)
<sqlalchemy.orm.mapper.Mapper object at 0x2af4d7004310>
>>> print dir(Region)
['__class__', '__delattr__', '__dict__', '__doc__',
... '__getattribute__', '__hash__', '__init__', '__module__',
... '__new__', '__reduce__', '__reduce_ex__', '__repr__',
... '__setattr__', '__str__', '__weakref__',
... '_sa_attribute_manager', 'c', 'id', 'name']
>>> print Region.id
<sqlalchemy.orm.mapper._CompileOnAttr object at 0x2af4d70046d0>
>>> print Region.name
<sqlalchemy.orm.mapper._CompileOnAttr object at 0x2af4d7004790>
>>> print Region.c.id
region.id
>>> print Region.c.name
region.name
Note
It is possible to make SQLAlchemy “forget” all the mappings that have been declared by invoking the clear_mappers() function. This feature can be useful when
prototyping various mappers within the interactive shell, as it will let you remap classes to try out different strategies.
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)
Other mapper() Parameters
1:N relations
M:N relations
As in the case of the 1:N join, we can also explicitly specify the join
criteria by using the primaryjoin (the join condition between the
table being mapped and the join table) and the secondaryjoin (the
join condition between the join table and the table being related to)
parameters:
mapper(Category, category_table, properties=dict(
products=relation(Product, secondary=product_category_table,
primaryjoin=(product_category_table.c.category_id
== category_table.c.id),
secondaryjoin=(product_category_table.c.product_id
== product_table.c.sku))))
mapper(Product, product_table, properties=dict(
categories=relation(Category, secondary=product_category_table,
primaryjoin=(product_category_table.c.product_id
== product_table.c.sku),
secondaryjoin=(product_category_table.c.category_id
== category_table.c.id))))
1:1 relations
Rather than specifying just the backref’s name, we can also use the
SQLAlchemy-provided backref() function. This function allows us to
pass along arguments to the relation that is created by the backref.
For instance, if we wanted to declare the product property on the
ProductSummary class rather than declaring the summary property on
the Product class, we could use backref() with uselist=False as
follows:
mapper(ProductSummary, product_summary_table, properties=dict(
product=relation(Product,
backref=backref('summary', uselist=False))))
mapper(Product, product_table)
Using a Self-Referential Mapper
However, we would also like to get the backref to the parent working
as well. For this, we need to specify the “remote side” of the backref.
In the case of the “parent” attribute, the “local side” is the parent_id
column, and the “remote side” is the id column. To specify the
remote side of a relation (or backref), we use the remote_side
parameter:
>>> mapper(Level, level_table, properties=dict(
... children=relation(Level,
... backref=backref('parent',
...
... remote_side=[level_table.c.id]))))
<sqlalchemy.orm.mapper.Mapper object at 0x1050990>
>>>
>>> l0 = Level('Gender')
>>> l1 = Level('Department', parent=l0)
>>> session.save(l0)
>>> session.flush()
2007-10-19 10:23:53,861 INFO sqlalchemy.engine.base.Engine.0x..50
... INSERT INTO level (parent_id, name) VALUES (?, ?)
2007-10-19 10:23:53,862 INFO sqlalchemy.engine.base.Engine.0x..50
... [None, 'Gender']
2007-10-19 10:23:53,875 INFO sqlalchemy.engine.base.Engine.0x..50
... INSERT INTO level (parent_id, name) VALUES (?, ?)
2007-10-19 10:23:53,876 INFO sqlalchemy.engine.base.Engine.0x..50
... [1, 'Department']
Note that a list is used for the remote_side parameter to allow for
compound foreign keys in the relation.
Cascading Changes to Related Objects
All of the cascade values in the following list refer to various functions
that are performed by the Session object (covered in more detail in
Chapter 7). The default value for the cascade parameter on a relation
is "save-update,merge".
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.)
Other relation() and backref() Parameters
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)
or the attribute:
mapper(Region, region_table, properties=dict(
stores=relation(Store,
collection_class=attribute_mapped_collection('name')))
If you wish to determine the key value to be used in some other way,
you can also use the SQLAlchemy-supplied MappedCollection class as
base class for your custom dict-like classes. MappedCollection takes
a keyfunc parameter in its constructor just like the
mapped_collection() function.
Extending Mappers
Sometimes you want to use the ORM to map objects that may exist
in multiple databases. SQLAlchemy provides support for “vertical”
partitioning (placing different kinds of objects or different tables in
different databases) as well as “horizontal” partitioning, also called
“sharding” (partitioning the rows of a single table across multiple
databases).
Vertical Partitioning
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")])
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)
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)
def query_chooser(query):
return ['even', 'odd']
Session = sessionmaker(class_=ShardedSession)
session = Session(
shard_chooser=shard_chooser,
id_chooser=id_chooser,
query_chooser=query_chooser,
shards=dict(even=engine1,
odd=engine2))
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)
Once you have a Session instance, you can begin persisting in-
memory objects. This is accomplished quite simply by calling the
save() method on the Session object. Suppose we have the following
schema and mapping:
from sqlalchemy import *
from sqlalchemy.orm import *
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)
Objects can have various states as they relate to Sessions. These states are defined as follows:
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]]
Updating Objects in the Session
mapper(Account, account_table)
Sessions have several utilities other than save() and delete() for
dealing with objects that they manage. These methods, as well as
save(), delete(), and a few query-related methods (covered in detail
later in this chapter, in Querying at the ORM Level”), are documented
here:
save(self, obj, entity=None)
Save the given object to the session. This operation cascades to
related objects according to the 'save-update' cascade rule.
If an entity name is specified, then use the named nonprimary
mapper() to persist the object.
delete(self, obj)
Mark the given object for deletion at the next flush().
expire(self, obj)
Mark the given object as no longer up-to-date. This causes any
mapped attributes to be refetched from the database the next
time they are accessed. This operation cascades to related
objects according to the 'refresh-expire' cascade rule.
refresh(self, obj)
Reload the object from the database with a fresh query. This
operation cascades to related objects according to the 'refresh-
expire' cascade rule.
merge(self, obj, entity=None)
Copy the state of the given object onto a persistent object with
the same database identity. This will either load an existing
Persistent instance from the database, modify one in memory, or
save a copy of the given obj. In none of these cases does the
object passed in become associated with the Session. This
operation cascades to related objects according to the 'merge'
cascade rule.
If an entity name is specified, then use the named nonprimary
mapper() to load or save the Persistent object.
expunge(self, obj)
Remove all references to obj from the Session. This operation
cascades to related objects according to the 'expunge' cascade
rule.
update(self, obj, entity=None)
Bring a given Detached obj into this session. This operation
cascades to related objects according to the 'save-or-update'
cascade rule.
If an entity name is specified, then use the named nonprimary
mapper() to load or save the Detached object.
get(self, class_, ident, **kwargs)
Return a Persistent instance of the object with the given class_
and identifier. (An object identifier is either the primary key
value if there is only one primary key in the underlying table, or
a tuple of primary keys in the case of a composite primary key.)
If an entity_name is specified as part of kwargs, then use the
named nonprimary mapper to map the class. The other kwargs
are passed unchanged to the underlying query() used to retrieve
the object.
load(self, class_, ident, **kwargs)
This is the same as the get() method with one exception: if the
object was already in the Session, the session will overwrite any
pending changes with fresh values from the database.
query(self, mapper_or_class, *addtl_entities, **kwargs)
Return a new Query object corresponding to this Session and the
given mapper_or_class.
close(self)
Clear the session and end any transactions in progress. This
restores the Session object to a “pristine” state, exactly the same
as when it was initially instantiated.
execute(self, clause, params=None, mapper=None, **kwargs)
This method is a thin wrapper around the underlying engine or
connection’s execute() method. (The clause, params, and kwargs
parameters are passed through unmodified, for instance.) It is
useful for executing SQL-level queries and updates within the
same transactional environment as your ORM queries and
updates. If the mapper parameter is specified, that mapper is
used to determine the engine on which to execute the query.
identity_map
The identity mapping between (class,identity) tuples and
objects in the session. Note that Persistent objects have an
_instance_key attribute attached to them, which is their Session
identity.
new
A collection of all Pending objects added to the Session since the
last flush().
dirty
A collection of all Persistent objects that have changes detected.
deleted
A collection of all Persistent objects that have been marked for
deletion via the Session delete() method.
Extending Sessions
Note that we can use mapped properties just like column objects in
SQL expressions. SQLAlchemy also provides access to the c attribute
(and all the attached columns) from the mapper’s underlying
selectable. In addition to this, SQLAlchemy provides a number of
methods on mapped properties to facilitate the construction of
complex queries. Some of these methods are summarized in the
following lists.
The following are methods on mapped columns:
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.
The true power of the SQLAlchemy ORM query system is really only
realized when using it to join across the relations defined in the
mapper() configuration. Joins can be performed across mapped
properties by using the join() method on the Query object. Once a
new class has been joined to the query, all its properties are available
for use in the filter() and filter_by() methods:
>>> query = session.query(Product)
>>> query = query.join('categories')
>>> query = query.filter_by(name='T-Shirts')
>>> print query.all()
[<Product 222>]
>>> print query
SELECT product.sku AS product_sku, product.msrp AS product_msrp
FROM product JOIN product_category ON product.sku =
... product_category.product_id JOIN category ON category.id =
... product_category.category_id
WHERE category.name = ? ORDER BY product.oid
Note that filter_by() used the Level’s name property, rather than the
Category’s name property, when performing the filter. SQLAlchemy
keeps track of a “joinpoint,” the last class referenced in an ORM join,
and applies any filter_by() criteria to that joinpoint until the
joinpoint changes. To manually reset the joinpoint to the “root” class,
simply call the reset_joinpoint() method.
Any new join() calls will also reset the joinpoint to the root of the
query. To disable this behavior (and continue joining from the current
joinpoint), simply specify from_joinpoint=True in the call to join().
As you may have noticed, the join() method constructs inner joins.
SQLAlchemy also provides an outerjoin() method for constructing
left outer joins. So, if we wanted to get a list of all products that have
no “Class” categorization or have a “Class” of “Pants,” we could
execute the following query:
>>> query = session.query(Product)
>>> query = query.outerjoin('categories')
>>> query = query.filter(or_(Category.c.name=='Pants',
... Category.c.name==None))
>>> print query
SELECT product.sku AS product_sku, product.msrp AS product_msrp
FROM product LEFT OUTER JOIN product_category ON product.sku =
... product_category.product_id LEFT OUTER JOIN category ON
... category.id = product_category.category_id
WHERE category.name = ? OR category.name IS NULL ORDER BY
... product.oid
>>> print query.all()
[<Product 123>, <Product 456>]
It is also possible to eagerly load where the LEFT OUTER JOIN is with
an alias. In this case, simply supply the alias (either as a string or as
an Alias object) to the contains_eager() alias parameter:
>>> session.clear()
>>> alias = category_table.alias('cat1')
>>> joined_product = product_table.outerjoin(product_category_table)
>>> joined_product = joined_product.outerjoin(alias)
>>> stmt = select([product_table, alias],
... from_obj=[joined_product])
>>> query = session.query(Product).from_statement(stmt)
>>> query = query.options(contains_eager('categories',
... alias='cat1'))
>>> session.bind.echo = True
>>> for prod in query:
... print prod, [c.name for c in prod.categories ]
...
2008-01-27 19:51:55,567 INFO sqlalchemy.engine.base.Engine.0x..90
... SELECT product.sku AS product_sku, product.msrp AS product_msrp,
... cat1.id AS cat1_id, cat1.level_id AS cat1_level_id,
... cat1.parent_id AS cat1_parent_id, cat1.name AS cat1_name
FROM product LEFT OUTER JOIN product_category ON product.sku =
... product_category.product_id LEFT OUTER JOIN category AS cat1 ON
... cat1.id = product_category.category_id
2008-01-27 19:51:55,567 INFO sqlalchemy.engine.base.Engine.0x..90 []
<Product 123> []
<Product 456> []
<Product 222> [u'Tops', u'Shirts', u'T-Shirts']
SQLAlchemy also supports creating objects from SQL where the main
table is aliased to another name. In this case, you must use the
contains_alias() mapper option. Again, you can pass either a string
name of the alias or the Alias object itself:
>>> alias = product_table.alias()
>>> stmt = alias.select()
>>> query = session.query(Product).from_statement(stmt)
>>> query = query.options(contains_alias(alias))
>>> print query.all()
[<Product 123>, <Product 456>, <Product 222>]
If you know a priori what objects you wish to construct, you can
create the query initially with this knowledge, rather than using the
add_entity() method:
>>> query = session.query(Product, Category)
>>> query =
... query.filter(Product.sku==product_category_table.c.product_id)
>>> query =
... query.filter(Category.id==product_category_table.c.category_id)
>>> for row in query:
... print row
...
(<Product 222>, <Category Department.Tops>)
(<Product 222>, <Category Class.Shirts>)
(<Product 222>, <Category SubClass.T-Shirts>)
Other Query Methods
The Query object has a number of other methods that allow great
flexibility. Some useful Query methods are summarized here:
add_column(self, column, id=None)
Add the named column to the query, making the query return a
tuple including the named column. id, if supplied, specifies that
the column will be correlated with the id parameter given to a
matching join() or outerjoin() method.
add_entity(self, entity, alias=None, id=None)
Add a class or mapper to the query, making the query return a
tuple including the given entity. If alias is supplied, the entity
will be aliased using the given alias. If id is supplied, the entity
will be selected from the join() or outerjoin() in the query with
a matching id parameter.
all( self )
Retrieve a list of results from the query (simply returns
list(self)).
autoflush(self, setting)
Sets the autoflushing behavior of the query (True or False). If
the query is autoflushing, the session will be flushed before the
query is executed, guaranteeing that in-memory objects are
consistent with query results. The default autoflush behavior of
the query is inherited from the session.
apply_avg(self, col)
Apply the SQL AVG() function to the given column and return the
resulting query.
apply_max(self, col)
Apply the SQL MAX() function to the given column and return the
resulting query.
apply_min(self, col)
Apply the SQL MIN() function to the given column and return the
resulting query.
apply_sum(self, col)
Apply the SQL SUM() function to the given column and return the
resulting query.
avg(self, col)
Execute the SQL AVG() function against the given column and
return the result.
count( self )
Execute the SQL COUNT() function against this query and return
the result. (count() takes other parameters, but they are
deprecated in SQLAlchemy 0.4.)
distinct( self )
Apply a SQL DISTINCT modifier to the query and return the
resulting query.
filter(self, criterion)
Apply the given SQL filtering criterion to the query and return the
resulting query. All filters are conjoined (combined using the SQL
AND operator).
filter_by(self, **kwargs)
Apply equality filtering criteria to the query and return the result.
The criteria are constructed based on the name, value pairs
supplied to the kwargs parameter.
first( self )
Execute the query and return the first result, or None if the query
has no results.
from_statement(self, statement)
Replace the underlying statement used by the query with the
given statement, which may be either a string of SQL or a query
constructed using the SQL expression language.
get(self, ident, reload=False, lockmode=None)
Retrieve an object based on the given identity from the session.
If the object is not currently loaded in the session, it will be
loaded. If reload is True, the object will be refreshed, regardless
of whether it is in the session. If lockmode is specified, the object
will be loaded with the given lockmode. The locking mode is
based around the idea of SELECT...FOR UPDATE and related
constructs. The lockmode value is inserted after the FOR
keyword.
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)
Create a join of this query based on a mapped property prop and
return the resulting query. prop can be either a string property
name or a list of string property names specifying a join path. If
id is specified, it should be a string for use in matching
add_column() or add_entity() id parameters. If aliased is True,
the joined entity will be aliased in the underlying query. If
from_joinpoint is True, the join will be from the last-joined
entity. Otherwise, it will be from the “root” entity of the query.
This method is typically used to add a filter based on some
related class.
limit(self, limit)
Apply a LIMIT modifier to the query and return the resulting
query. Note that SQLAlchemy generates appropriate SQL to
make the LIMIT apply to the objects generated, not the rows.
This is done to return the specified number of objects even in
the presence of JOINs.
load(self, ident, raiseerr=True, lockmode=None)
Return an instance of the object based on the given ident,
refreshing the object from the database. This is similar to get()
with reload=True, but will raise an error if the object is not
found in the database.
max(self, col)
Execute the SQL MAX() function against the given column and
return the result.
min(self, col)
Execute the SQL MIN() function against the given column and
return the result.
offset(self, offset)
Apply an OFFSET modifier to the query and return the resulting
query. Note that SQLAlchemy generates appropriate SQL to
make the OFFSET apply to the objects generated, not the rows,
in order to skip the specified number of objects even in the
presence of JOINs.
one( self )
Return the first result of the query, raising an exception if the
query does not return exactly one result.
options(self, *args)
Return a new query with the mapper options (such as
eagerload(), etc.) listed in args applied.
order_by(self, criterion)
Apply a SQL ORDER BY modifier to the query and return the
resulting query.
outerjoin(self, prop, id=None, aliased=False,
from_joinpoint=False)
Create a LEFT OUTER JOIN of this query based on a mapped
property prop and return the resulting query. prop can be either
a string property name or a list of string property names
specifying a join path. If id is specified, it should be a string for
use in matching add_column() or add_entity() id parameters. If
aliased is True, the joined entity will be aliased in the underlying
query. If from_joinpoint is True, the join will be from the last-
joined entity. Otherwise, it will be from the “root” entity of the
query. This method is typically used to add a filter based on
some related class.
params(self, *args, **kwargs)
Add values for bind parameters that exist in the underlying
query. The binding dictionary may be passed as keyword
arguments or as a dict in the first positional argument.
populate_existing( self )
Return a query that will refresh all objects loaded. Normally,
when a query executes, it will not modify any objects already in
memory. This option changes that behavior.
query_from_parent(cls, instance, property, **kwargs) (classmethod)
Create a new Query object that returns objects with a
relationship to a given object instance through the named
property. The kwargs are passed along unmodified to the Query
constructor. This is mainly used internally to SQLAlchemy, to
construct queries for lazily loaded properties.
reset_joinpoint( self )
Reset the joinpoint of the query to the “root” mapper. This
affects subsequent calls to filter_by() and possibly to join()
and outerjoin().
sum(self, col)
Execute the SQL SUM() function against the given column and
return the result.
with_lockmode(self, mode)
Return a new Query object using the specified locking mode.
with_parent(self, instance, property=None)
Add a join criterion based on a relationship to a mapped
instance via the named property. If property is not supplied,
SQLAlchemy attempts to infer an appropriate property.
__getitem__(self, item) (indexing)
If item is a slice object, apply appropriate OFFSET and LIMIT
modifers to the query to emulate the Python slicing operation. If
item is an integer, apply an appropriate OFFSET with a LIMIT of
1, execute the query, and return the result.
__iter__(self)(iteration)
Returns an iterator that will build mapped objects from the
query.
Contextual or Thread-Local Sessions
we can now declare them like this (assuming that Session has
already been declared globally as a contextual Session):
Session.mapper(Product, product_table, properties=dict(
categories=relation(Category, secondary=product_category_table,
backref='products')))
Once we have mapped the classes as shown, we can use the mapped
classes themselves to perform session-like functions:
>>> Product.query().all()
[<Product 123>, <Product 456>, <Product 222>, <Product 333>]
>>> prod = Product('444', msrp=55.66)
>>> Product.query().all()
[<Product 123>, <Product 456>, <Product 222>, <Product 333>,
... <Product 444>]
Using the contextual session mapper() method also gives us one other
benefit: a reasonably usable default constructor. This constructor
allows us to provide values for any of the properties defined in the
mapped class via keyword arguments. So, if we omitted the Product
constructor and used Session.mapper() to map it, we could initialize
products as follows:
>>> p = Product(sku='555', msrp=22.11)
Chapter 8. Inheritance Mapping
In this chapter, you will learn the different methods of mapping
object-oriented inheritance to relational database tables. You will
learn how to use different methods of inheritance mapping with
SQLAlchemy, as well as how to use inheritance in the presence of
mapped relations between classes.
Overview of Inheritance Mapping
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
Notice that we have constructed a table that contains columns for all
of the attributes across the entire hierarchy we wish to model. This
means that we incur some overhead for all of the classes in the
hierarchy in each row. Although this doesn’t cause too many
problems with the simple hierarchy we are using in this example, the
space overhead can become significant with larger and more
attribute-rich hierarchies.
Also note that we have introduced a new column, the 'product_type'
column. This column holds the “polymorphic identity” of each row, so
named because it allows SQLAlchemy to return the appropriate class
from a query on the parent object. The polymorphic identity is used
by SQLAlchemy to determine what type of object is contained in the
row. SQLAlchemy supports using any data type desired to hold this
information; here we use a single character. 'P' will represent a
Product (the parent class), 'C' will represent a Clothing product, and
'A' will represent an Accessory product.
To map this table onto our inheritance hierarchy, we will use some
new keyword arguments to the mapper() function, namely
polymorphic_on, inherits, and polymorphic_identity:
mapper(
Product, product_table,
polymorphic_on=product_table.c.product_type,
polymorphic_identity='P')
mapper(Clothing, inherits=Product,
polymorphic_identity='C')
mapper(Accessory, inherits=Product,
polymorphic_identity='A')
Aside from the space overhead, there is one problem in using single
table inheritance mapping: the mapper will try to map all the columns
of the single table unless you manually specify columns to map at
each level of the inheritance hierarchy via the include_columns or
exclude_columns arguments to the mapper. For instance, if we try to
get the clothing_info for a nonclothing product, SQLAlchemy will not
complain:
>>> print session.query(Accessory)[0].clothing_info
None
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))
clothing_table = Table(
'clothing', metadata,
Column('sku', None, ForeignKey('product.sku'),
primary_key=True),
Column('clothing_info', String))
accessory_table = Table(
'accessory', metadata,
Column('sku', None, ForeignKey('product.sku'),
primary_key=True),
Column('accessory_info', String))
As you can see, the various types of products are selected from their
tables appropriately. Note, however, that the single query() call
yielded not one, but five SELECT statements. This is due to the fact
that SQLAlchemy must perform an auxiliary query for each row that
represents a child object. The next section shows how we can
improve performance in this situation.
Optimizing Performance with Joined Table Inheritance Mapping
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')
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
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)
The main difference between Elixir versions 0.4 and 0.5 is in the way your entities get transformed into SQLAlchemy tables and mappers. In version 0.4, Elixir introduced the
idea of “autosetup,” where entities were “set up” when they were first accessed. Under 0.4, you could delay the setup of an entity by specifying autosetup=False in the
using_options() DSL statement. In this case, you would need to manually set up the entity at some point before using it by calling either setup_all(), which will set
up all entities defined, or setup_entities(entities), which will set up all the entities in the entities list.
In version 0.5, entities do not use autosetup by default, so you are responsible for manually applying either setup_all() or setup_entities() once all your entities
have been defined. If you would still like to use autosetup, you can either specify autosetup=True for each entity in its using_options() statement or specify that all
entities should use autosetup via:
elixir.options_defaults['autosetup'] = True
In version 0.5, autosetup is not only not the default, but also “is not recommended” according to the official Elixir documentation. So, using setup_all() is probably the
most “future-proof” way of defining your model.
There are several interesting things to notice in the Elixir listing. First,
note that the declaration of the tables has been moved inside the
class definitions, and that the classes are derived from Elixir’s Entity
class. This is in keeping with Elixir’s “active record” model, where the
mapped classes are responsible for “remembering” the necessary
data for persisting themselves. Second, notice that we didn’t declare
any primary keys for the store or the price tables. If no primary key is
declared, then Elixir will autogenerate an integer primary key with a
sequence providing default values. Third, notice that the relationships
were declared according to their behavior in the ORM (OneToMany,
ManyToOne), and that no foreign key information was included in the
model. Elixir will, based on the types of relationships declared,
automatically generate foreign key columns as well as any auxiliary
tables required for ManyToMany joins.
Note
Because of the various types of assumptions Elixir makes about table layout, it is suited mainly for “blue sky” development, where there is no need to maintain an existing
legacy database, and where the primary schema definition exists in Python, not in SQL. It is possible to use Elixir where Elixir does not provide the primary schema definition,
but it’s easy to shoot yourself in the foot if you’re not aware of the assumptions Elixir makes about the schema, particularly when dealing with autogenerated tables and
columns.
Installing Elixir
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')
Field has_field
Attribute class DSL function
ManyToOne belongs_to
OneToMany has_many
OneToOne has_one
ManyToMany has_and_belongs_to_many
Note
Unlike SQLAlchemy, Elixir currently requires that your entities be defined in a module (or in several modules) and imported; they cannot be defined at the interactive Python
prompt. This is due partly to the fact that Elixir uses the module name in determining how to “autoname” the tables it creates.
In Elixir, most columns are defined via the Field() class (attribute
syntax) and/or the has_field() statement (DSL syntax). The Field
constructor has only one required argument, its type. There are also
some optional arguments parsed by Elixir. Any remaining arguments
are passed along to the SQLAlchemy Column constructor. The Elixir-
parsed optional keyword arguments are described here:
required
Specifies whether the field can be set to None (corresponds to
the inverse of the nullable option in the Column constructor).
Defaults to False unless this is a primary key column, in which
case it defaults to True.
colname
The name of the column to be used for this field. By default it
will be the same as the name used for the attribute.
deferred
If True, use deferred loading on the underlying Column object. If
set to a string value, add the underlying Column to the named
deferred loading column group.
synonym
Specifies a synonym value for the field. This is equivalent to
using the synonym() function in SQLAlchemy.
Like the Field constructor, the has_field() statement passes along
unrecognized keyword arguments to the Column constructor.
has_field() takes two required arguments: the name of the field
being defined and its type. Elixir also supports the following optional
arguments:
through
The name of a relation to go through to get the field. This uses
the associationproxy SQLAlchemy extension, which is described
in Chapter 11. This allows proxying fields from a related class
onto the class being mapped. The relation must be with only one
object, of course, via ManyToOne / belongs_to() or OneToOne /
has_one().
attribute
The name of the attribute on the related object used in
conjunction with the through parameter. If this is omitted, the
name of the current field will be used.
With the through and attribute arguments to has_field(), we can
proxy a related class’s attribute as follows:
class Price(Entity):
has_field('price', Numeric, default=0)
belongs_to('product', of_kind='Product')
belongs_to('store', of_kind='Store')
has_field('store_name', through='store', attribute='name')
has_field('sku', through='product')
Using this definition of the Price entity and the definitions of Product
and Store used previously (all saved in a module named model.py),
let’s import the model, create the database, and see what Elixir does
in the background:
>>> from elixir import *
>>> from model import *
>>>
>>> create_all()
>>>
>>> stores = [ Store('Main Store'),
... Store('Secondary Store') ]
>>> products = [
... Product('123', 11.22),
... Product('456', 33.44),
... Product('789', 123.45) ]
>>> prices = [ Price(product=product, store=store, price=10.00)
... for product in products
... for store in stores ]
>>>
>>> session.flush()
This will create all the tables used to implement the entities
defined up to this point.
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.
In some cases, you may need to have access to the underlying table
to define an Entity’s properties, particularly when creating properties
that correspond to calculated SQL values that were handled by
SQLAlchemy’s column_property() function. This presents a problem in
Elixir, as the underlying Table objects have not been created when
the Fields are being defined. Elixir solves this problem by allowing
fields to be created in a “deferred” manner. Elixir supports this in the
attribute-based syntax via the GenericProperty and ColumnProperty
classes, and in the DSL syntax via the has_property() statement.
Each of these methods of defining deferred properties takes a
callable, which will be passed the underlying Table object’s c attribute
and should return a property to be added to the Entity’s mapper. In
the case of ColumnProperty, rather than returning a property object,
you simply return a ClauseElement, which will be wrapped in a
SQLAlchemy column_property():
class Product(Entity):
has_field('sku', String(20), primary_key=True)
has_field('msrp', Numeric)
In all of the relations supported by Elixir, you must “name” the Entity to which you are relating the mapped Entity. If the related Entity is in the same module as the
Entity being mapped, simply use the name of the Entity. Otherwise, you need to give a module path to the other entity. If you defined Entity1 in package/model1.py
and Entity2 in package/model2.py, and Entity2 needs a ManyToOne relationship to Entity1, you would define Entity2 as follows:
class Entity2(Entity):
entity1=ManyToOne('package.model1.Entity')
Attribute-based syntax
Elixir automatically generates a few arguments of its own to pass to the relation() function, so they should not be provided to the relation-creating classes unless you are
trying to override the value provided by Elixir. These arguments are uselist, remote_side, secondary, primaryjoin, and secondaryjoin.
DSL syntax
Like the has_field() statement, all the DSL relation statements take
the optional parameters through and via in order to proxy attributes
of the related class(es) to the mapped class. See the earlier section
Fields and Properties” for more information on these parameters.
All of the keyword arguments supported in the attribute-based syntax
are also supported in the DSL syntax. Refer to Table 9-1 earlier in this
chapter for the correspondence between attribute-based classes and
DSL statements.
Inheritance
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)
One of the nice things about Elixir is that the Entity base class
contains a rich set of methods that can be used instead of the normal
SQLAlchemy Session and Query methods. In fact, each Entity
contains a class-level attribute named query that returns a query on
the mapped class. It is also unnecessary to explicitly save() entities
to the Session, as they are automatically save()d when they are
created.
To retrieve a mapped object from its identity (primary key), simply
use the get() method. (In “base” SQLAlchemy, this would be
accomplished by Session.get( class_, id).)
>>> Product.get('123')
<Product 123>
Elixir also adds the get_by() method for retrieving a single instance
based on nonprimary key columns. (The corresponding query in
SQLAlchemy is a filter_by() followed by one().)
>>> Product.get_by(msrp=33.44)
<Product 456>
Of course, you can always access the underlying Session query via
the query attribute:
>>> Product.query.all()
[<Product 123>, <Product 456>, <Product 789>]
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()
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
SQLAlchemy
This chapter describes SqlSoup, an extension to SQLAlchemy that
provides automatic mapping of introspected tables. You will learn
how to use SqlSoup to map to an existing database and how to
perform queries and updates. Finally, the chapter will describe the
pros and cons of using SQLSoup, Elixir, or “bare” SQLAlchemy in your
application.
Introduction to SqlSoup
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")])
Note that there was no mapper or table setup required to retrieve the
objects (other than when we first created the database!). The
following sections describe in more detail how you can use SqlSoup.
Using SqlSoup for ORM-Style Queries and Updates
You may have noticed in the previous section that when we queried
the db.product table, rather than being served with RowProxy objects
as in regular SQLAlchemy, we were served with MappedProduct
instances. This is because technically we’re not selecting from the
product table; we’re selecting from the automatically created and
mapped MappedProduct class, created from the product table.
The MappedProduct class provides a basic mapping of the columns of
the table to the properties of the class. It also provides a query
property, similar to the Elixir query property, which provides access to
a session query for the MappedProduct. It also provides insert(),
delete(), and update() methods for modifying the underlying data.
To create a new product, for instance, we can do the following:
>>> newprod = db.product.insert(sku='111', msrp=22.44)
>>> db.flush()
>>> db.clear()
>>> db.product.all()
[MappedProduct(sku='123',msrp=Decimal("12.34")),
... MappedProduct(sku='456',msrp=Decimal("22.12")),
... MappedProduct(sku='789',msrp=Decimal("41.44")),
... MappedProduct(sku='111',msrp=Decimal("22.44"))]
You may have noticed in the previous example that we accessed the
session-like methods flush() and clear() on the SqlSoup instance.
SqlSoup strives to provide a rich set of functionality with a limited set
of interfaces, namely the SqlSoup instance and automatically mapped
classes. As such, the SqlSoup instance provides several session-like
functions as well as providing access to the automatically mapped
classes:
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.
You may have also noticed that the MappedProduct class provides
some query-like methods. In fact, the MappedProduct class (and other
automatically mapped classes) uses some __getattr__() magic to
forward all unrecognized attribute and method access to its query
attribute. Automatically mapped classes also provide some data
manipulation functions for use in updating the underlying table:
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.
SqlSoup and Relations
The short story on SqlSoup and SQLAlchemy relation()s is that they are not supported. Although SqlSoup can make reasonable assumptions about how to automatically
map columns to classes, inferring the correct relations, relation names, and relation options is currently beyond its capabilities. SqlSoup does, however, fully support manually
creating joins between tables and mapping the resulting selectable object. This feature is covered next in Joins with SqlSoup.”
Joins with SqlSoup
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)]
In some cases, it’s nice to label the columns according to their table
of origin. To accomplish this, use the with_labels() SqlSoup method:
>>> join3 = db.with_labels(join1)
>>> join3.first()
MappedJoin(product_sku='123',product_msrp=Decimal("12.34"),
... product_price_sku='123',product_price_store_id=1,
... product_price_price=Decimal("0"))
>>> db.with_labels(join2).first()
MappedJoin(product_sku='123',product_msrp=Decimal("12.34"),
... product_price_sku='123',product_price_store_id=1,
... product_price_price=Decimal("0"),store_id=1,store_name='Main
... Store')
It is also possible to label a mapped table and then use the labeled
table in joins:
>>> labelled_product = db.with_labels(db.product)
>>> join4 = db.join(labelled_product, db.product_price,
... isouter=True)
>>> join4.first()
MappedJoin(product_sku='123',product_msrp=Decimal("12.34"),sku='123',
... store_id=1,price=Decimal("0"))
Note that the columns from db.product are labeled, whereas the
columns from db.product_price are not.
Mapping Arbitrary Selectables
Simple tables and joins are supported in SqlSoup, but what about
mapping more complex selectables? The automatically mapping
machinery of SqlSoup is actually exposed via the SqlSoup map()
method. For instance, if we wished to add a column for the average
price of a product over all the stores in which it is sold, we might
write the following SQL-layer SQLAlchemy query:
>>> db.clear()
>>>
>>> from sqlalchemy import *
>>>
>>> join5 = db.join(db.product, db.product_price)
>>>
>>> s = select([db.product._table,
... func.avg(join5.c.price).label('avg_price')],
... from_obj=[join5._table],
... group_by=[join5.c.sku])
>>> s = s.alias('products_with_avg_price')
>>> products_with_avg_price = db.map(s, primary_key=[join5.c.sku])
>>> products_with_avg_price.all()
[MappedJoin(sku='123',msrp=Decimal("12.34"),avg_price=0.0),
... MappedJoin(sku='456',msrp=Decimal("22.12"),avg_price=0.0),
... MappedJoin(sku='789',msrp=Decimal("41.44"),avg_price=0.0)]
>>>
>>> db.product_price.first().price = 50.00
>>> db.flush()
>>> db.clear()
>>> products_with_avg_price.all()
[MappedJoin(sku='123',msrp=Decimal("12.34"),avg_price=25.0),
... MappedJoin(sku='456',msrp=Decimal("22.12"),avg_price=0.0),
... MappedJoin(sku='789',msrp=Decimal("41.44"),avg_price=0.0)]
There’s no magic here; this is just Python’s ability to declare new, ad-
hoc attributes on existing objects. Do note that if you happen to add
an attribute with the same name as a table in your database,
SqlSoup will not be able to access that table until you remove the
new attribute.
Directly Accessing the Session
Deletes
SQLAlchemy
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')
We can even append onto the users attribute to add new SalesReps.
To enable this functionality, however, we need to create some
sensible constructors for our mapped objects:
class User(object):
def __init__(self, user_name=None, password=None):
self.user_name=user_name
self.password=password
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
Now, we can populate the database and add a user as a sales rep to
a brand:
>>> Session = sessionmaker(bind=engine)
>>> engine.echo = True
>>> session = Session()
>>>
>>> b = Brand('Cool Clothing')
>>> session.save(b)
>>>
>>> u = User('rick', 'foo')
>>> session.save(u)
>>>
>>> metadata.bind.echo = True
>>> session.flush()
2007-11-23 12:48:28,304 INFO sqlalchemy.engine.base.Engine.0x..90
... BEGIN
2007-11-23 12:48:28,305 INFO sqlalchemy.engine.base.Engine.0x..90
... INSERT INTO user (user_name, password) VALUES (?, ?)
2007-11-23 12:48:28,306 INFO sqlalchemy.engine.base.Engine.0x..90
... ['rick', 'foo']
2007-11-23 12:48:28,308 INFO sqlalchemy.engine.base.Engine.0x..90
... INSERT INTO brand (name) VALUES (?)
2007-11-23 12:48:28,308 INFO sqlalchemy.engine.base.Engine.0x..90
... ['Cool Clothing']
>>> b.users
2007-11-23 12:48:31,317 INFO sqlalchemy.engine.base.Engine.0x..90
... SELECT sales_rep.brand_id AS sales_rep_brand_id,
... sales_rep.user_id AS sales_rep_user_id, sales_rep.commission_pct
... AS sales_rep_commission_pct
FROM sales_rep
WHERE ? = sales_rep.brand_id ORDER BY sales_rep.oid
2007-11-23 12:48:31,318 INFO sqlalchemy.engine.base.Engine.0x..90
... [1]
[]
>>> b.users.append(u)
2007-11-23 12:48:33,668 INFO sqlalchemy.engine.base.Engine.0x..90
... SELECT sales_rep.brand_id AS sales_rep_brand_id,
... sales_rep.user_id AS sales_rep_user_id, sales_rep.commission_pct
... AS sales_rep_commission_pct
FROM sales_rep
WHERE ? = sales_rep.user_id ORDER BY sales_rep.oid
2007-11-23 12:48:33,669 INFO sqlalchemy.engine.base.Engine.0x..90
... [1]
>>> b.users
[<__main__.User object at 0xbdc710>]
>>> b.sales_reps
[<__main__.SalesRep object at 0xbe4610>]
>>> b.sales_reps[0].commission_pct
0
>>> session.flush()
2008-01-27 21:12:35,991 INFO sqlalchemy.engine.base.Engine.0x..50
... INSERT INTO sales_rep (brand_id, user_id, commission_pct) VALUES
... (?, ?, ?)
2008-01-27 21:12:35,994 INFO sqlalchemy.engine.base.Engine.0x..50
... [1, 1, 0]
reps_by_user_class=attribute_mapped_collection('user')
Note that the proxy and the original relation are automatically kept
synchronized by SQLAlchemy:
>>> print b.sales_reps_by_user[u]
<__main__.SalesRep object at 0xbf2750>
>>> print b.sales_reps_by_user[u].commission_pct
20
Ordering List
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()
A link in an index entry is displayed as the section title in which that entry appears. Because some sections have multiple index markers, it is not unusual for an entry to have
several links to the same section. Clicking on any link will take you directly to the place in the text in which the marker appears.
Symbols
K
key argument (Column constructor), Column Definitions
key method (ResultProxy), Database Connections and ResultProxys
keys( ) method (ResultProxy), Database Connections and
ResultProxys