Re: [sqlalchemy] [Q] Lack of isolation in unit tests
Hello. OK, that's unusual but that is actually the case where clear_mappers() is fine to use. SQLAlchemy's own unit tests create new classes for each test because we're testing SQLAlchemy itself. OK, yes this is correct, as the compilation step tries to get at all mappers. Sorry I didn't realize this, it's very strange for end-user unit tests to be testing the creation of ad-hoc mapper configurations. Thank you for your clarification. At this time I still play with/learn SQLAlchemy and unit tests are the fastest way to learn it, at least for me. I don't want to write new module file with a single test case to play with, hence I define ORM classes locally in each test and test my assumptions about how SQLAlchemy works :-) Ladislav Lenart On 18.9.2012 17:40, Michael Bayer wrote: On Sep 18, 2012, at 11:36 AM, Ladislav Lenart wrote: Hello. Adding call to clear_mappers() to tearDown fixed my problem. For the rest of the discussion I am not sure I completely follow. I use declarative exclusively but each test defines its own Base and its own set of ORM classes. OK, that's unusual but that is actually the case where clear_mappers() is fine to use. SQLAlchemy's own unit tests create new classes for each test because we're testing SQLAlchemy itself. However when one test has a bug in an ORM class definition, ALL the following tests fail because of this. OK, yes this is correct, as the compilation step tries to get at all mappers. Sorry I didn't realize this, it's very strange for end-user unit tests to be testing the creation of ad-hoc mapper configurations. -- 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.
[sqlalchemy] [Q] Lack of isolation in unit tests
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.
Re: [sqlalchemy] [Q] Lack of isolation in unit tests
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.
Re: [sqlalchemy] [Q] Lack of isolation in unit tests
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
Re: [sqlalchemy] [Q] Lack of isolation in unit tests
On Tue, Sep 18, 2012 at 11:07 AM, Ladislav Lenart lenart...@volny.cz wrote: def create_base(): return declarative_base(cls=_Base) Move the declaration of _Base to within create_base, and I think that should fix your problem. (I've had a similar one, not with test cases, but with a replica schema) -- 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.
Re: [sqlalchemy] [Q] Lack of isolation in unit tests
Hello. Adding call to clear_mappers() to tearDown fixed my problem. For the rest of the discussion I am not sure I completely follow. I use declarative exclusively but each test defines its own Base and its own set of ORM classes. However when one test has a bug in an ORM class definition, ALL the following tests fail because of this. I tried to delete all ORM classes defined in the test but this did not help. Perhaps there are lingering references to them somewhere. I am not sure how Python's GC works. Anyway, thank you again for your quick help, Ladislav Lenart On 18.9.2012 16:39, Michael Bayer wrote: 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
Re: [sqlalchemy] [Q] Lack of isolation in unit tests
On Sep 18, 2012, at 11:36 AM, Ladislav Lenart wrote: Hello. Adding call to clear_mappers() to tearDown fixed my problem. For the rest of the discussion I am not sure I completely follow. I use declarative exclusively but each test defines its own Base and its own set of ORM classes. OK, that's unusual but that is actually the case where clear_mappers() is fine to use. SQLAlchemy's own unit tests create new classes for each test because we're testing SQLAlchemy itself. However when one test has a bug in an ORM class definition, ALL the following tests fail because of this. OK, yes this is correct, as the compilation step tries to get at all mappers. Sorry I didn't realize this, it's very strange for end-user unit tests to be testing the creation of ad-hoc mapper configurations. -- 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.