- bugfixes, behavioral changes
- general
- orm
+ - Exceptions raised during compile_mappers() are now
+ preserved to provide "sticky behavior" - if a hasattr()
+ call on a pre-compiled mapped attribute triggers a failing
+ compile and suppresses the exception, subsequent compilation
+ is blocked and the exception will be reiterated on the
+ next compile() call. This issue occurs frequently
+ when using declarative.
+
- Corrected problem with Query.delete() and
Query.update() not working properly with bind
parameters. [ticket:1242]
# Disable any attribute-based compilation.
self.compiled = True
manager = self.class_manager
-
+ if hasattr(self, '_compile_failed'):
+ del self._compile_failed
if not self.non_primary and manager.mapper is self:
manager.mapper = None
manager.events.remove_listener('on_init', _event_on_init)
return object.__getattribute__(self, key)
class_mapper(cls)
-
+
if cls.__dict__.get(clskey) is self:
# if this warning occurs, it usually means mapper
# compilation has failed, but operations upon the mapped
_COMPILE_MUTEX.acquire()
try:
- global _already_compiling
- if _already_compiling:
- # re-entrance to compile() occurs rarely, when a class-mapped construct is
- # used within a ForeignKey, something that is possible
- # when using the declarative layer
- self._post_configure_properties()
- return
- _already_compiling = True
try:
-
- # double-check inside mutex
- if self.compiled and not _new_mappers:
+ global _already_compiling
+ if _already_compiling:
+ # re-entrance to compile() occurs rarely, when a class-mapped construct is
+ # used within a ForeignKey, something that is possible
+ # when using the declarative layer
+ self._post_configure_properties()
+ return
+ _already_compiling = True
+ try:
+
+ # double-check inside mutex
+ if self.compiled and not _new_mappers:
+ return self
+
+ # initialize properties on all mappers
+ for mapper in list(_mapper_registry):
+ if getattr(mapper, '_compile_failed', False):
+ raise sa_exc.InvalidRequestError("One or more mappers failed to compile. Exception was probably "
+ "suppressed within a hasattr() call. "
+ "Message was: %s" % mapper._compile_failed)
+ if not mapper.compiled:
+ mapper._post_configure_properties()
+
+ _new_mappers = False
return self
-
- # initialize properties on all mappers
- for mapper in list(_mapper_registry):
- if not mapper.compiled:
- mapper._post_configure_properties()
-
- _new_mappers = False
- return self
- finally:
- _already_compiling = False
+ finally:
+ _already_compiling = False
+ except:
+ import sys
+ exc = sys.exc_info()[1]
+ self._compile_failed = exc
+ raise
finally:
_COMPILE_MUTEX.release()
to execute once all mappers have been constructed.
"""
+
self._log("_post_configure_properties() started")
l = [(key, prop) for key, prop in self._props.iteritems()]
for key, prop in l:
prop.init(key, self)
self._log("_post_configure_properties() complete")
self.compiled = True
-
+
def add_properties(self, dict_of_properties):
"""Add the given dictionary of properties to this mapper,
using `add_property`.
from sqlalchemy import exc
from testlib import sa, testing
from testlib.sa import MetaData, Table, Column, Integer, String, ForeignKey, ForeignKeyConstraint, asc
-from testlib.sa.orm import relation, create_session, class_mapper, eagerload, compile_mappers, backref
+from testlib.sa.orm import relation, create_session, class_mapper, eagerload, compile_mappers, backref, clear_mappers
from testlib.testing import eq_
from orm._base import ComparableEntity, MappedTest
Base = decl.declarative_base(testing.db)
def tearDown(self):
+ clear_mappers()
Base.metadata.drop_all()
def test_basic(self):
foo = sa.orm.column_property(User.id == 5)
self.assertRaises(sa.exc.InvalidRequestError, go)
+ def test_nice_dependency_error_works_with_hasattr(self):
+ class User(Base):
+ __tablename__ = 'users'
+ id = Column('id', Integer, primary_key=True)
+ addresses = relation("Addresss")
+
+ # doesn't raise, hasattr() squashes all exceptions
+ # (except KeybaordInterrupt/SystemException in 2.6...whoopee)
+ # TODO: determine what hasattr() does on py3K
+ hasattr(User.id, 'in_')
+ # but the exception is saved, compile_mappers tells us what it is
+ # as well as some explaination
+ self.assertRaisesMessage(sa.exc.InvalidRequestError, r"suppressed within a hasattr\(\)", compile_mappers)
+
def test_custom_base(self):
class MyBase(object):
def foobar(self):
from testlib import sa, testing
from testlib.sa import MetaData, Table, Column, Integer, String, ForeignKey, func
from testlib.sa.engine import default
-from testlib.sa.orm import mapper, relation, backref, create_session, class_mapper, reconstructor, validates, aliased
+from testlib.sa.orm import mapper, relation, backref, create_session, class_mapper, compile_mappers, reconstructor, validates, aliased
from testlib.sa.orm import defer, deferred, synonym, attributes, column_property, composite, relation, dynamic_loader, comparable_property
from testlib.testing import eq_, AssertsCompiledSQL
import pickleable
self.assertRaises(sa.exc.ArgumentError,
relation, Address, cascade="fake, all, delete-orphan")
+ @testing.resolve_artifact_names
+ def test_exceptions_sticky(self):
+ mapper(Address, addresses, properties={
+ 'user':relation(User)
+ })
+ hasattr(Address.id, 'in_')
+ self.assertRaisesMessage(sa.exc.InvalidRequestError, r"suppressed within a hasattr\(\)", compile_mappers)
+
@testing.resolve_artifact_names
def test_column_prefix(self):
mapper(User, users, column_prefix='_', properties={