Using SQLAlchemy with Pylons

来源:互联网 发布:python logging 编辑:程序博客网 时间:2024/05/29 06:32

This chapter shows how to set up your model for SQLAlchemy 0.4 (not 0.3). (It has not been updated for SQLAlchemy 0.5-beta.) It's not the only way to use SQLAlchemy with Pylons, but it's a flexible approach that covers most situations, including applications with multiple databases. SQLAlchemy is a front end to several relational databases including MySQL, PostgreSQL, SQLite, MS-SQL, Oracle, etc. It allows you to work on three different levels, even in the same application:

  • The object-relational mapper (ORM) lets you interact with the database using your own object classes rather than writing SQL code.
  • The SQL expression language has many methods to create customized SQL statements, and the result cursor is more friendly than DBAPI's.
  • The low-level execute methods accept literal SQL strings if you find something the SQL builder can't do, such as adding a column to an existing table or modifying the column's type. If they return results, you still get the benefit of SQLAlchemy's result cursor.

The first two levels are database neutral, meaning they hide the differences between the databases' SQL dialects. Changing to a different database is merely a matter of supplying a new connection URL. Of course there are limits to this, but SQLAlchemy is 90% easier than rewriting all your SQL queries.

The SQLAlchemy manual should be your next stop for questions not covered here. It's very well written and thorough.

Throughout this chapter, "myapp" refers to your Pylons application's package directory (e.g., MyApp-1.0.1.egg/myapp).

The Pylons development version (which will become Pylons 0.9.7) will ask when you create your application whether you intend to use SQLAlchemy, and will preconfigure it for you. In this case, you'll find that many of the steps below are already done. Pylons 0.9.6 does not do this, so you'll have to make all the changes by hand. Under the Attachments tab on this page you'll find a Pylons 0.9.6.1 application containing the code here. The application won't do anything because we've neglected the user interface, but you can examine the code or paste it into your own application. The Pylons Cookbook contains more advanced database tutorials.

Install SQLAlchemy

We'll assume you've already installed Pylons and have the easy_install command. At the command line, run:

1
easy_install SQLAlchemy

Next you'll have to install a database engine and its Python bindings. If you don't know which one to choose, SQLite is a good one to start with. It's small and easy to install, and Python 2.5 includes bindings for it. Installing the database engine is beyond the scope of this article, but here are the Python bindings you'll need for the most popular engines:

123
easy_install pysqlite             # If you use SQLite and Python 2.4 (not needed for Python 2.5)easy_install MySQL-python         # If you use MySQLeasy_install psycopg2             # If you use PostgreSQL

See the Python Package Index (formerly the Cheeseshop) for other database drivers.

Check Your Version

To see which version of SQLAlchemy you have, go to a Python shell and look at sqlalchemy.__version__ :

123
>>> import sqlalchemy>>> sqlalchemy.__version__0.4.3

These instructions assume SQLAlchemy 0.4.2p3 or newer. They will not work with SQLAlchemy 0.3.

Model

Metadata

Create myapp/model/meta.py containing:

 1 2 3 4 5 6 7 8 91011121314
"""SQLAlchemy Metadata and Session object"""from sqlalchemy import MetaData__all__ = ['engine', 'metadata', 'Session']# SQLAlchemy database engine.  Updated by model.init_model().engine = None# SQLAlchemy session manager.  Updated by model.init_model().Session = None# Global metadata. If you have multiple databases with overlapping table # names, you'll need a metadata for each database.metadata = MetaData()

A SQLAlchemy engine is a pool of connections to a particular database. The metadata is an object that will contain your table definitions. The Session is used with the object-relational mapper.

Main model module

Change myapp/model/_init_.py to read:

 1 2 3 4 5 6 7 8 9101112
import sqlalchemy as safrom sqlalchemy import ormfrom myapp.model import metadef init_model(engine):    """Call me before using any of the tables or classes in the model."""    sm = orm.sessionmaker(autoflush=True, transactional=True, bind=engine)    meta.engine = engine    meta.Session = orm.scoped_session(sm)

Note that this function sets attributes in a different module. The reason is that these attributes depend on a live database engine, which may not exist when the model is imported. So we call this function to complete the initialization.

transactional=True means all ORM operations will be done within a database transaction. autoflush=True means SQLAlchemy will automatically call Session.flush() to write the changes to the database whenever we commit the transaction by calling Session.commit(). The transactional and autoflush options are normally either both true or both false.

bind=engine tells the ORM session to use that database for all operations. If you're using multiple databases it gets a little more complicated, as we'll see below.

You may of course use other sessionmaker or scoped_session arguments if you wish.

Tables and ORM classes

If you have only a couple simple tables you can put them in the main model module directly. Otherwise you can put them in separate modules, one per table, one per group of tables, or however you wish. Here's a simple table and its ORM class:

 1 2 3 4 5 6 7 8 9101112131415
import sqlalchemy as safrom sqlalchemy import ormfrom myapp.model import metat_dictionary = sa.Table("Dictionary", meta.metadata,    sa.Column("id", sa.types.Integer, primary_key=True),    sa.Column("term", sa.types.String(100), nullable=False),    sa.Column("definition", sa.types.String, nullable=False),    )class Dictionary(object):    passorm.mapper(Dictionary, t_dictionary)

If you've put your tables into separate modules, you can optionally import them into the main model module. This is not required but it allows you to access them in your controllers and in "paster shell" by just importing the model. Examples:

12
from myapp.model import dictionaryfrom myapp.model.dictionary import Dictionary

Relation example

Here's an example of a Person and an Address class with a many:many relationship on people.my_addresses. See Relational Databases for Poeople in a Hurry and the SQLAlchemy manual for details.

 1 2 3 4 5 6 7 8 91011121314151617181920212223242526272829303132
import sqlalchemy as safrom sqlalchemy import ormfrom myapp.model import metat_people = sa.Table('people', meta.metadata,    sa.Column('id', sa.types.Integer, primary_key=True),    sa.Column('name', sa.types.String(100)),    sa.Column('email', sa.types.String(100)))t_addresses_people = sa.Table('addresses_people', meta.metadata,    sa.Column('id', sa.types.Integer, primary_key=True),    sa.Column('person_id', sa.types.Integer, sa.ForeignKey('people.id')),    sa.Column('address_id', sa.types.Integer, sa.ForeignKey('addresses.id')))t_addresses = sa.Table('addresses', meta.metadata,    sa.Column('id', sa.types.Integer, primary_key=True),    sa.Column('address', sa.types.String(100)))class Person(object):    passclass Address(object):    passorm.mapper(Address, t_addresses)orm.mapper(Person, t_people, properties = {    'my_addresses' : orm.relation(Address, secondary = t_addresses_people),    })

Reflecting tables

If you want SQLAlchemy to read the table structure from existing database tables so you don't have to specify the columns, you'll have to put the table definitions and the mapper calls inside init_model because they depend on a live database connection. The ORM class defintions do not have to be in init_model. So you could do something like:

 1 2 3 4 5 6 7 8 910111213141516
import sqlalchemy as safrom sqlalchemy import ormfrom myapp.model import metafrom myapp.model import recordsdef init_model(engine):    """Call me before using any of the tables or classes in the model."""    sm = orm.sessionmaker(autoflush=True, transactional=True, bind=engine)    meta.engine = engine    meta.Session = orm.scoped_session(sm)    records.t_record = sa.Table("Record", meta.metadata, autoload=True, autoload_with=engine)    orm.mapper(records.Record, records.t_record)

Using the model standalone

You now have everything necessary to use the model in a standalone script such as a cron job, or to test it interactively. You just need to create a SQLAlchemy engine and connect it to the model. This example uses a database "test.sqlite" in the current directory:

12345678
% pythonPython 2.5.1 (r251:54863, Oct  5 2007, 13:36:32)[GCC 4.1.3 20070929 (prerelease) (Ubuntu 4.1.2-16ubuntu2)] on linux2Type "help", "copyright", "credits" or "license" for more information.>>> import sqlalchemy as sa>>> engine = sa.create_engine("sqlite:///test.sqlite")>>> from myapp import model>>> model.init_model(engine)

Now you can use the tables, classes, and Session as described in the SLQAlchemy manual.

The config file

When your Pylons application runs, it needs to know which database to connect to. Normally you put this information in development.ini and activate the model in environment.py. Put the following in development.ini in the [app:main] section, depending on your database,

For SQLite

1
sqlalchemy.url = sqlite:///%(here)s/mydatabasefilename.sqlite

Where mydatabasefilename.db is the path to your SQLite database file. "%(here)s" represents the directory containing the development.ini file. If you're using an absolute path, use four slashes after the colon: "sqlite:////var/lib/myapp/database.sqlite". Don't use a relative path (three slashes) because the current directory could be anything. The example has three slashes because the value of "%(here)s" always starts with a slash (or the platform equivalent; e.g., "C:
foo" on Windows).

For MySQL

12
sqlalchemy.url = mysql://username:password@host:port/databasesqlalchemy.pool_recycle = 3600

Enter your username, password, host (localhost if it is on your machine), port number (usually 3306) and the name of your database. The second line is an example of setting engine options.

It's important to set "pool_recycle" for MySQL to prevent "MySQL server has gone away" errors. This is because MySQL automatically closes idle database connections without informing the application. Setting the connection lifetime to 3600 seconds (1 hour) ensures that the connections will be expired and recreated before MySQL notices they're idle.

Don't be tempted to use the ".echo" option to enable SQL logging because it may cause duplicate log output. Instead see the "Logging" section below to integrate MySQL logging into Paste's logging system.

For PostgreSQL

1
sqlalchemy.url = postgres://username:password@host:port/database

Enter your username, password, host (localhost if it is on your machine), port number (usually 5432) and the name of your database.

The engine

Put this at the top of myapp/config/environment.py:

12
from sqlalchemy import engine_from_configfrom myapp.model import init_model

And this in the load_environment function:

12
engine = engine_from_config(config, 'sqlalchemy.')init_model(engine)

The second argument is the prefix to look for. If you named your keys "sqlalchemy.default.url", you would put "sqlalchemy.default." here. The prefix may be anything, as long as it's consistent between the config file and this function call.

Controller

Add the following to the top of myapp/lib/base.py (the base controller):

1
from myapp.model import meta

And change the .__call__ method to:

12345
def __call__(self, environ, start_response):    try:        return WSGIController.__call__(self, environ, start_response)    finally:        meta.Session.remove()

The .remove() method is very important! It discards any leftover ORM data in the current web request. Otherwise the stray data will leak into the next request handled by this thread, potentially causing errors or data corruption.

Any per-request behaviors can be configured at this stage. For example, to use just a single database connection per request, which removes all connection pool checkin/checkout overhead, the per-request Session can be configured with a Connection:

12345678
def __call__(self, environ, start_response):    conn = meta.engine.connect()    meta.Session.configure(bind=conn)    try:        return WSGIController.__call__(self, environ, start_response)    finally:        meta.Session.remove()        conn.close()

Note that when using a session with transactional=True, the session holds onto a single connection through the lifespan of each transaction so the above optimization is not as significant.

Building the database

To actually create the tables in the database, you call the metadata's .create_all() method. You can do this interactively or use paster's application initialization feature. To do this, put the code in myapp/websetup.py. After the load_environment() call, put:

1234
from myapp.model import metalog.info("Creating tables")meta.metadata.create_all(bind=meta.engine)log.info("Successfully setup")

Then run the following on the command line:

1
paster setup-app development.ini

Data queries and modifications

Important: this section assumes you're putting the code in a high-level model function. If you're putting it directly into a controller method, you'll have to put a model. prefix in front of every object defined in the model, or import the objects individually. Also note that the Session object here (capital s) is not the same as the Beaker session object (lowercase s) in controllers.

Here's how to enter new data into the database:

1234
mr_jones = Person()mr_jones.name = 'Mr Jones'meta.Session.save(mr_jones)meta.Session.commit()

mr_jones here is an instance of Person. Its properties correspond to the column titles of t_people and contain the data from the selected row. A more sophisticated application would have a Person.__init__ method that automatically sets attributes based on its arguments.

An example of loading a database entry in a controller method, performing a sex change, and saving it:

12345
person_q = meta.Session.query(Person)            # An ORM Query object for accessing the Person tablemr_jones = person_q.filter(Person.name=='Mr Jones').one()print mr_jones.name                                    # prints 'Mr Jones'mr_jones.name = 'Mrs Jones'                            # only the object instance is changed here ...meta.Session.commit()                                  # ... only now is the database updated

To return a list of entries use:

1
all_mr_joneses = person_q.filter(Person.name=='Mr Jones').all()

To get all list of all the people in the table use:

1
everyone = person_q.all()

To retrieve by id:

1
someuser = person_q.get(5)

You can iterate over every person even more simply:

1234567
print "All people"for p in person_q:    print p.nameprintprint "All Mr Joneses:"for p in person_q.filter(Person.name=='Mr Jones'):    print p.name

To delete an entry use the following:

123
mr_jones = person_q.filter(Person.name=='Mr Jones').one()meta.Session.delete(mr_jones)meta.Session.commit()

Working with joined objects

Recall that the my_addresses property is a list of Address objects

1
print mr_jones.my_addresses[0].address         # prints first address

To add an existing address to 'Mr Jones' we do the following:

1234
address_q = meta.Session.query(Address)address = address_q.filter(Address.address=='33 Pine Marten Lane, Pleasantville').one()     # Retrieve an existing addressmr_jones.my_addresses.append(new_address)                                      # Add to the listmeta.Session.commit()                                                          # issue updates to the join table

To add an entirely new address to 'Mr Jones' we do the following:

1234
new_address = Address()                                      # Construct an empty address objectnew_address.address = '33 Pine Marten Lane, Pleasantville'mr_jones.my_addresses.append(new_address)                    # Add to the listmeta.Session.commit()                                        # Commit changes to the database

After making changes you must call meta.Session.commit() to store them permanently in the database; otherwise they'll be discarded at the end of the web request. You can also call meta.Session.rollback() at any time to undo any changes that haven't been committed.

To search on a joined object we can pass an entire object as a query:

123
search_address = Address()search_address.address = '33 Pine Marten Lane, Pleasantville'residents_at_33_pine_marten_lane = person_q.filter(Person.my_addresses.contains(search_address)).all()

  • All attributes must match in the query object.

Or we can can search on a joined objects' property,

1
residents_at_33_pine_marten_lane = person_q.join('my_addresses').filter(Address.address=='33 Pine Marten Lane, Pleasantville').all()

A shortcut for the above is to use any():

1
residents_at_33_pine_marten_lane = person_q.filter(Person.my_addresses.any(Address.address=='33 Pine Marten Lane, Pleasantville')).all()

To disassociate an address from Mr Jones we do the following:

12
del mr_jones.my_addresses[0]                        # Delete the reference to the addressmeta.Session.commit()

To delete the address itself in the address table, normally we'd have to issue a separate delete() for the Address object itself:

123
meta.Session.delete(mr_jones.my_addresses[0])                   # Delete the Address objectdel mr_jones.my_addresses[0]meta.Session.commit()                                    # Commit both operations to the database

However, SQLAlchemy supports a shortcut for the above operation. Configure the mapper relation using cascade = "all, delete-orphan" instead:

1234
orm.mapper(Address, t_addresses)orm.mapper(Person, t_people, properties = {    'my_addresses' : orm.relation(Address, secondary=t_addresses_people, cascade="all,delete-orphan"),    })

Then, any items removed from mr_jones.my_addresses is automatically deleted from the database:

12
del mr_jones.my_addresses[0]                        # Delete the reference to the address, also deletes the Addressmeta.Session.commit()

For any relationship, you can add cascade = "all, delete-orphan" as an extra argument to relation() in your mappers to ensure that when a join is deleted the joined object is deleted as well, so that the above delete() operation is not needed - only the removal from the my_addresses list. Beware though that despite its name, delete-orphan removes joined objects even if another object is joined to it.

Non-ORM SQL queries

Use meta.Session.execute() to execute a non-ORM SQL query within the session's transaction. Bulk updates and deletes can modify records significantly faster than looping through a query and modifying the ORM instances.

 1 2 3 4 5 6 7 8 91011121314151617181920
q = sa.select([table1.c.id, table1.c.name], order_by=[table1.c.name])records = meta.Session.execute(q).fetchall()# Example of a bulk SQL UPDATE.update = table1.update(table1.c.name=="Jack")meta.Session.execute(update, name="Ed")meta.Session.commit()# Example of updating all matching records using an expression.update = table1.update(values={table1.c.entry_id: table1.c.entry_id + 1000})meta.Session.exececute(update)meta.Session.commit()# Example of a bulk SQL DELETE.delete = table1.delete(table1.c.name.like("M%"))meta.Session.execute(delete)meta.Session.commit() # Database specific, use only if SQLAlchemy doesn't have methods to construct the desired query.meta.Session.execute("ALTER TABLE Foo ADD new_column (VARCHAR(255)) NOT NULL")

Warning: the last example changes the database structure and may adversely interact with ORM operations.

Further reading

The Query object has may other features, including filtering on conditions, ordering the results, grouping, etc. These are excellently described in the SQLAlchemy manual. See especially the Data Mapping and Session / Unit of Work chapters.

Testing Your Models

Normal model usage works fine in model tests, however to use the metadata you must specify an engine connection for it. To have your tables created for every unit test in your project, use a test_models.py such as:

 1 2 3 4 5 6 7 8 91011
from myapp.tests import *from myapp import modelfrom myapp.model import metaclass TestModels(TestController):    def setUp(self):        meta.Session.remove()        meta.metadata.create_all(meta.engine)        def test_index(self):        # test your models

Notice that the tests inherit from TestController. This is to ensure that the application is setup so that the models will work.

"nosetests --with-pylons=/path/to/test.ini ..." is another way to ensure that your model is properly initialized before the tests are run. This can be used when running non-controller tests.

Multiple engines

Some applications need to connect to multiple databases (engines). Some always bind certain tables to the same engines (e.g., a general database and a logging database); this is called "horizontal partitioning". Other applications have several databases with the same structure, and choose one or another depending on the current request. A blogging app with a separate database for each blog, for instance. A few large applications store different records from the same logical table in different databases to prevent the database size from getting too large; this is called "vertical partitioning" or "sharding". The pattern above can accommodate any of these schemes with a few minor changes.

First, you can define multiple engines in your config file like this:

sqlalchemy.default.url = "mysql://..."sqlalchemy.default.pool_recycle = 3600sqlalchemy.log.url = "sqlite://..."

This defines two engines, "default" and "log", each with its own set of options. Now you have to instantiate every engine you want to use.

123
default_engine = engine_from_config(config, 'sqlalchemy.default.')log_engine = engine_from_config(config, 'sqlalchemy.log.')init_model(default_engine, log_engine)

Of course you'll have to modify init_model() to accept both arguments and create two engines.

To bind different tables to different databases, but always with a particular table going to the same engine, use the binds argument to sessionmaker rather than bind:

12
binds={"table1": engine1, "table2": engine2}Session = scoped_session(sessionmaker(transactional=True, autoflush=True, binds=binds)

To choose the bindings on a per-request basis, skip the sessionmaker bind(s) argument, and instead put this in your base controller's __call__ method before the superclass call, or directly in a specific action method:

1
meta.Session.configure(bind=meta.engine)

binds= works the same way here too.

Discussion on coding style, the Session object, and bound metadata

All ORM operations require a Session and an engine. All non-ORM SQL operations require an engine. (Strictly speaking, they can use a connection instead, but that's beyond the scope of this tutorial.) You can either pass the engine as the bind= argument to every SQLAlchemy method that does an actual database query, or bind the engine to a session or metadata. This tutorial recommends binding the session because that is the most flexible, as shown in the "Multiple Engines" section above.

It's also possible to bind a metadata to an engine using the MetaData(engine) syntax, or to change its binding with metadata.bind = engine. This would allow you to do autoloading without the autoload_with argument, and certain SQL operations without specifying an engine or session. Bound metadata was common in earlier versions of SQLAlchemy but is no longer recommended for beginners because it can cause unexpected behavior when ORM and non-ORM operations are mixed.

Don't confuse SQLAlchemy sessions and Pylons sessions; they're two different things! The session object used in controllers (pylons.session) is an industry standard used in web applications to maintain state between web requests by the same user. SQLAlchemy's session is an object that synchronizes ORM objects in memory with their corresponding records in the database.

The Session variable in this chapter is not a SQLAlchemy session object; it's a "contextual session" class. Calling it returns the (new or existing) session object appropriate for this web request, taking into account threading and middleware issues. Calling its class methods (Session.commit(), Session.query(...), etc) implicitly calls the corresponding method on the appropriate session. You can normally just call the Session class methods and ignore the internal session objects entirely. See "Contextual/Thread-local Sessions" in the SQLAlchemy manual for more information. This is equivalent to SQLAlchemy 0.3's SessionContext but with a different API.

"Transactional" sessions are a new feature in SQLAlchemy 0.4; this is why we're using Session.commit() instead of Session.flush(). The transactional and autoflush args to sessionmaker enable this, and should normally be used together.

Contextual session mapper

If you're looking for the equivalent of SQLAlchemy 0.3's "assign_mapper" function, here's the syntax:

12
# Instead of the regular mapper calls.meta.Session.mapper(MyClass, table1)

See Associating Classes and Mappers with a Contextual Session for a description of what it does. This method enables magical behavior which can surprise unwary users, so make sure you understand mappers, queries, sessions, and scoped_session() before doing this.

Fancy classes

Here's an ORM class with some extra features:

 1 2 3 4 5 6 7 8 910111213141516171819202122232425262728293031323334353637
class Person(object):    def __init__(self, firstname, lastname, sex):        if not firstname:  raise ValueError("arg 'firstname' cannot be blank")        if not lastname:  raise ValueError("arg 'lastname' cannot be blank")        if sex not in ["M", "F"]:  raise ValueError("sex must be 'M' or 'F'")        self.firstname = firstname        self.lastname = lastname        self.sex = sex    def __repr__(self):        myclass = self.__class__.__name__        return "<%s %s %s>" % (myclass, self.firstname, self.lastname)        #return "%s(%r, %r)" % (myclass, self.firstname, self.lastname, self.sex)        #return "<%s %s>" % (self.firstname, self.lastname)    @property    def name(self):        return "%s %s" % (self.firstname, self.lastname)    @classmethod    def all(class_, order=None, sex=None):        """Return a Query of all Persons.  The caller can iterate this, do q.count(), add additional        conditions, etc.        """        q = meta.Session.query(Person)        if order and order.lower().startswith("d"):            q = q.order_by([Person.birthdate.desc()])        else:            q = q.order_by([Person.lastname, Person.firstname])        return q    @classmethod    def recent(self, cutoff_days=30):        cutoff = datetime.date.today() - datetime.timedelta(days=cutoff_days)        q = meta.Session.query(Person).order_by([Person.last_transaction_date.desc()])        q = q.filter(Person.last_transaction_date >= cutoff)        return q

With this class you can create new records with constructor args. This is not only convenient but ensures the record starts off with valid data (no required field empty). .__init__ is not called when loading an existing record from the database, so it doesn't interfere with that. Instances can print themselves in a friendly way, and a read-only property is calculated from multiple fields.

Class methods return high-level queries for the controllers. If you don't like the class methods you can have a separate PersonSearch class for them. The methods get the session from the myapp.model.meta module where we've stored it. Note that this module imported the meta module, not the Session object directly. That's because init_model() replaces the Session object, so if we'd imported the Session object directly we'd get its original value rather than its current value.

You can do many more things in SQLAlchemy, such as a read-write property on a hidden column, or specify relations or default ordering in the orm.mapper call. You can make a composite property like person.location.latitude and person.location.longitude where latitude and longitude come from different table columns. You can have a class that mimics a list or dict but is associated with a certain table. Some of these properties you'll make with Pylons normal property mechanism; others you'll do with the property argument to orm.mapper. And you can have relations up the gazoo, which can be lazily loaded if you don't use one side of the relation much of the time, or eagerly loaded to minimize the number of queries. (Only the latter use SQL joins.) You can have certain columns in your class lazily loaded too, although SQLAlchemy calls this "deferred" rather than "lazy". SQLAlchemy will automatically load the columns or related table when they're accessed.

If you have any more clever ideas for fancy classes, please add a comment to this article.

Logging

SQLAlchemy has several loggers that chat about the various aspects of its operation. To log all SQL statements executed along with their parameter values, put the following in development.ini:

1234
[logger_sqlalchemy]level = INFOhandlers = qualname = sqlalchemy.engine

Then modify the "[loggers]" section to enable your new logger:

12
[loggers]keys = root, myapp, sqlalchemy

To log the results along with the SQL statements, set the level to DEBUG. This can cause a lot of output! To stop logging the SQL, set the level to WARN or ERROR.

SQLAlchemy has several other loggers you can configure in the same way. "sqlalchemy.pool" level INFO tells when connections are checked out from the engine's connection pool and when they're returned. "sqlalchemy.orm" and buddies log various ORM operations. See "Configuring Logging" in the SQLAlchemy manual.

Multiple application instances

If you're running multiple instances of the same Pylons application in the same WSGI process (e.g., with Paste HTTPServer's "composite" application), you may run into concurrency issues. The problem is that Session is thread local but not application-instance local. We're not sure how much this is really an issue if Session.remove() is properly called in the base controller, but just in case it becomes an issue, here are possible remedies:

1) Attach the engine(s) to pylons.g (aka. config["pylons.g"]) rather than to the meta module. The globals object is not shared between application instances.

2) Add a scoping function. This prevents the application instances from sharing the same session objects. Add the following function to your model, and pass it as the second argument to scoped_session:

123456
def pylons_scope():    import thread    from pylons import config    return "Pylons|%s|%s" % (thread.get_ident(), config._current_obj())Session = scoped_session(sessionmaker(...), pylons_scope)

If you're affected by this, or think you might be, please bring it up on the pylons-discuss mailing list. We need feedback from actual users in this situation to verify that our advice is correct.

Third-party front ends

Elixir

If you're familiar with ActiveRecord, used in Ruby on Rails, then you may want to use the Elixir layer on top of SQLAlchemy. You can check the Pylons Cookbook for a tutorial, or look at the pylons-discuss list archive, especially this thread.

Tesla

Tesla is a framework built on top of Pylons and Elixir/SQLAlchemy.Tutorial (not sure if it's current?)

Comments  (Hide)

If you want to unittest your code you need to do a bit of extra work to set things up and make sure no stray database will be left behind. The code below should do the trick.

 1 2 3 4 5 6 7 8 9101112131415
import osfrom unittest import TestCasefrom sqlalchemy import create_enginefrom myapp.model import meta# If myapp.model does  not import all your models you need to do that here so create_all# will know how to create their tables and indices.class TestSessionUtils(TestCase):    def setUp(Self):        meta.engine = create_engine("sqlite:///test.sqlite")        meta.metadata.create_all(meta.engine)    def tearDown(self):        meta.Session.remove()        os.unlink("test.sqlite")

Posted by Wichert Akkerman at Mar 24, 2008 10:48 | Permalink

That code turned out to be almost correct: it still got all the data from functional tests. Extending the setup code like below fixes that.

 1 2 3 4 5 6 7 8 91011121314151617
import osfrom unittest import TestCasefrom sqlalchemy import create_enginefrom myapp.model import metafrom myapp.model import init_model# If myapp.model does  not import all your models you need to do that here so create_all# will know how to create their tables and indices.class TestSessionUtils(TestCase):    def setUp(Self):        meta.engine=create_engine("sqlite:///test.sqlite")        init_model(meta.engine)        meta.metadata.create_all(meta.engine)    def tearDown(self):        meta.Session.remove()        os.unlink("test.sqlite")

Posted by Wichert Akkerman at Mar 24, 2008 11:03 | Permalink

Please change the fourth line of code in this block http://wiki.pylonshq.com/display/pylonsdocs/Using+SQLAlchemy+with+Pylons#UsingSQLAlchemywithPylons-Mainmodelmodule

from:

1
from myapp.model import meta

to:

1
import meta

I was trying to upgrade a Pylons project from 0.9.6.1 on my Windows box and I was getting SQLAlchemy errors: "Table 'clients' is already defined for this MetaData instance". I added some debugging code and it seems that the module _init_.py was imported twice (but metadata stayed the same as it was in another module).

The second time the module was imported by itself. After changing the line above the module is imported only once and everything works.

Posted by Wojciech Malinowski at Apr 28, 2008 22:50 | Permalink
原创粉丝点击