the _mapper_registry is weak referencing, so doesn't have any impact on mappers 
hanging around or not.   Ultimately, the mapper is associated with your mapped 
class.   The clear_mappers() API call will de-associate mappers from classes 
and remove instrumentation that was affixed by the mapper() call.

However, the vast majority of applications have no need for such a phase, and 
there is no benefit to calling clear_mappers().   This is discussed in the docs 
for clear_mappers(): 
http://docs.sqlalchemy.org/en/rel_0_7/orm/mapper_config.html?highlight=clear_mappers#sqlalchemy.orm.clear_mappers

If you're using declarative, it is especially useless, since the directives 
placed on a declarative class are destroyed by the mapping process in the first 
place, and those classes can't be "re-mapped" unless you defined the mappings 
again non-declaratively, thus making the usage of declarative in the first 
place completely redundant.

The mapped class is best considered as a single, composed unit.    Just like 
when you create a class in your application that has, for example, an 
__init__() method and a get_stuff() method, there's no need when tearing down 
tests to remove the __init__() and get_stuff() methods from those classes; 
these are part of that structure.    

A frequent misunderstanding is that mappers have some connection to the 
database, either through engines or connections, and that as long as mappers 
exist, this means we are "connected" to the database.  This is not at all true, 
any more than a SQL string you might pass to cursor.execute() in DBAPI has any 
relationship to database connections.    The mapper() only represents in-memory 
information about how your classes are structured in terms of hypothetical 
database tables.   It has no connection whatsoever to actual database 
connections.

When unit testing, the thing that needs to be torn down is the transactional, 
connection, and Session state, that is, those things which represent per-use 
resources that need to be closed down.    An example of how to associate a 
Session with connection resources for the lifespan of a test is here: 
http://docs.sqlalchemy.org/en/rel_0_7/orm/session.html#joining-a-session-into-an-external-transaction
 .    It seems like you're already working with this code based on what I see 
below.   



On Sep 18, 2012, at 10:20 AM, Ladislav Lenart wrote:

> Hello again.
> 
> The problem is in sqlalchemy.orm.mapper. There are globals _mapper_registry 
> and
> a boolean _new_mappers that triggers recompilation of the mappers. Is there a
> safe way to clear them in each test's case tearDown?
> 
> Thank you in advance,
> 
> Ladislav Lenart
> 
> 
> On 18.9.2012 16:07, Ladislav Lenart wrote:
>> Hello.
>> 
>> I have isolation issues with unit testing with SQLAlchemy. My DbTestCase 
>> looks
>> like this (I use nose test framework):
>> 
>> 
>> class DbTestCase(object):
>>    """Db-aware test case superclass."""
>>    __engine_name__ = 'postgres'
>>    __db_name__ = 'unit_tests'
>>    __echo__ = True
>> 
>>    @property
>>    def engine(self):
>>        return self.Base.metadata.bind
>> 
>>    def setUp(self):
>>        self.Base = create_base()
>>        self._create_db()
>>        self.connection = self.engine.connect()
>>        self.trans = self.connection.begin()
>>        self.session = Session(bind=self.connection)
>> 
>>    def _create_db(self):
>>      # construct conn_string from __engine_name__ and __db_name__
>>        engine = create_engine(conn_string, echo=echo)
>>        self.Base.metadata.bind = engine
>> 
>>    def tearDown(self):
>>        # Rollback: Everything that happened with the Session above (including
>>        # calls to commit()) is rolled back.
>>        self.trans.rollback()
>>        self.session.close()
>> 
>>        # Return connection to the engine.
>>        self.connection.close()
>> 
>>        # Remove all tables while we know what we have defined.
>>        self._drop_all()
>> 
>>    def _recreate_all(self):
>>        self._drop_all()
>>        self._create_all()
>> 
>>    def _drop_all(self):
>>        self.Base.metadata.drop_all()
>> 
>>    def _create_all(self):
>>        self.Base.metadata.create_all()
>> 
>> 
>> A complete usage looks like this:
>> 
>> import re
>> from sqlalchemy import Column, Integer, ForeignKey
>> from sqlalchemy.orm import relationship
>> from sqlalchemy.ext.declarative import declarative_base, declared_attr
>> 
>> 
>> class _Base(object):
>>    @declared_attr
>>    def __tablename__(cls): #@NoSelf
>>        return camel_case_to_underscore(cls.__name__)
>> 
>> 
>> def camel_case_to_underscore(name):
>>    """Convert CamelCaseForm to camel_case_form.
>>    Taken from:
>> http://stackoverflow.com/questions/1175208/elegant-python-function-to-convert-camelcase-to-camel-case
>>    """
>>    s1 = re.sub('(.)([A-Z][a-z]+)', r'\1_\2', name)
>>    return re.sub('([a-z0-9])([A-Z])', r'\1_\2', s1).lower()
>> 
>> 
>> def create_base():
>>    return declarative_base(cls=_Base)
>> 
>> 
>> class Id(object):
>>    """Add id column as primary key."""
>> 
>>    @declared_attr
>>    def id(cls): #@NoSelf
>>        return Column(Integer(), primary_key=True)
>> 
>> 
>> class TestFooBar(DbTestCase):
>>    def test_foo(self):
>>        class Foo(self.Base, Id):
>>            qux = relationship('Qux') # <-- intentional bug
>>            pass
>> 
>>        self._recreate_all()
>> 
>>        foo = Foo()
>>        self.session.add(foo)
>>        self.session.flush()
>>        # ...
>> 
>>    def test_foo_bar(self):
>>        class Foo(self.Base, Id):
>>            bars = relationship('Bar', back_populates='foo')
>> 
>>        class Bar(self.Base, Id):
>>            foo_id = Column(Integer(), ForeignKey('foo.id'), nullable=False)
>>            foo = relationship('Foo', back_populates='bars')
>> 
>> 
>>        self._recreate_all()
>> 
>>        foo = Foo()
>>        bar = Bar(foo=foo)
>>        self.session.add(foo)
>>        self.session.add(bar)
>>        self.session.flush()
>>        # ...
>> 
>> When I run the above two tests, BOTH fail because of the bug in test_foo. The
>> test test_foo_bar fails with:
>> 
>> InvalidRequestError: One or more mappers failed to initialize - can't proceed
>> with initialization of other mappers.  Original exception was: When 
>> initializing
>> mapper Mapper|Foo|foo, expression 'Qux' failed to locate a name ("name 'Qux' 
>> is
>> not defined"). If this is a class name, consider adding this relationship() 
>> to
>> the <class 'zfp.tests.model.test_foo.Foo'> class after both dependent classes
>> have been defined.
>> 
>> If I comment the 'qux = ...' line, BOTH tests pass.
>> 
>> I thought that using separate declarative bases, each with its own metadata, 
>> is
>> enough to ensure isolation among tests. Was my assumption wrong? Can I 
>> achieve
>> proper isolation of unit tests somehow?
>> 
>> 
>> Thank you,
>> 
>> Ladislav Lenart
>> 
>> 
> 
> 
> -- 
> You received this message because you are subscribed to the Google Groups 
> "sqlalchemy" group.
> To post to this group, send email to sqlalchemy@googlegroups.com.
> To unsubscribe from this group, send email to 
> sqlalchemy+unsubscr...@googlegroups.com.
> For more options, visit this group at 
> http://groups.google.com/group/sqlalchemy?hl=en.
> 

-- 
You received this message because you are subscribed to the Google Groups 
"sqlalchemy" group.
To post to this group, send email to sqlalchemy@googlegroups.com.
To unsubscribe from this group, send email to 
sqlalchemy+unsubscr...@googlegroups.com.
For more options, visit this group at 
http://groups.google.com/group/sqlalchemy?hl=en.

Reply via email to