]> git.ipfire.org Git - thirdparty/sqlalchemy/sqlalchemy.git/commitdiff
- mapper compilation has been reorganized such that most compilation
authorMike Bayer <mike_mp@zzzcomputing.com>
Sun, 2 Sep 2007 19:55:33 +0000 (19:55 +0000)
committerMike Bayer <mike_mp@zzzcomputing.com>
Sun, 2 Sep 2007 19:55:33 +0000 (19:55 +0000)
  occurs upon mapper construction.  this allows us to have fewer
  calls to mapper.compile() and also to allow class-based properties
  to force a compilation (i.e. User.addresses == 7 will compile all
  mappers; this is [ticket:758]).  The only caveat here is that
  an inheriting mapper now looks for its inherited mapper upon construction;
  so mappers within inheritance relationships need to be constructed in
  inheritance order (which should be the normal case anyway).

14 files changed:
CHANGES
VERSION
lib/sqlalchemy/engine/base.py
lib/sqlalchemy/orm/__init__.py
lib/sqlalchemy/orm/attributes.py
lib/sqlalchemy/orm/mapper.py
lib/sqlalchemy/topological.py
lib/sqlalchemy/util.py
test/orm/inheritance/basic.py
test/orm/inheritance/polymorph.py
test/orm/inheritance/polymorph2.py
test/orm/inheritance/productspec.py
test/orm/mapper.py
test/orm/unitofwork.py

diff --git a/CHANGES b/CHANGES
index 4c197522f10041a84d787bf5c18f070b31428d99..5a956f8ecebfead2460e7fef60fb3ad563751da6 100644 (file)
--- a/CHANGES
+++ b/CHANGES
@@ -1,6 +1,17 @@
 =======
 CHANGES
 =======
+0.4.0beta6
+----------
+
+- mapper compilation has been reorganized such that most compilation
+  occurs upon mapper construction.  this allows us to have fewer
+  calls to mapper.compile() and also to allow class-based properties
+  to force a compilation (i.e. User.addresses == 7 will compile all
+  mappers; this is [ticket:758]).  The only caveat here is that 
+  an inheriting mapper now looks for its inherited mapper upon construction;
+  so mappers within inheritance relationships need to be constructed in
+  inheritance order (which should be the normal case anyway).
 
 0.4.0beta5
 ----------
diff --git a/VERSION b/VERSION
index ee9e5bbc70bfbe144cd5cb4821dda7034d0fe85f..378853f694d23a74cbb594136dbae3f7171fc8e3 100644 (file)
--- a/VERSION
+++ b/VERSION
@@ -1 +1 @@
-0.4.0beta5
+0.4.0beta6
index 45d84f90d1a01efd4d3f98196e2a2480a4896d82..bd6f5b97cf4c491381ebbde110348b8f68b5becc 100644 (file)
@@ -1663,7 +1663,6 @@ class DefaultRunner(schema.SchemaVisitor):
     def visit_sequence(self, seq):
         """Do nothing.
 
-        Sequences are not supported by default.
         """
 
         return None
index 40806bdb0a308fa85ea6bc432d903a99269a4f32..0d4dec4bc4a19fe2a7ab33b8585daff4c3a1fe62 100644 (file)
@@ -529,18 +529,15 @@ def compile_mappers():
 def clear_mappers():
     """Remove all mappers that have been created thus far.
 
-    When new mappers are created, they will be assigned to their
-    classes as their primary mapper.
+    The mapped classes will return to their initial "unmapped"
+    state and can be re-mapped with new mappers.
     """
-
     mapperlib._COMPILE_MUTEX.acquire()
     try:
         for mapper in mapper_registry.values():
             mapper.dispose()
         mapper_registry.clear()
-        # TODO: either dont use ArgSingleton, or
-        # find a way to clear only ClassKey instances from it
-        sautil.ArgSingleton.instances.clear()
+        mapperlib.ClassKey.dispose()
     finally:
         mapperlib._COMPILE_MUTEX.release()
         
index 74e9eb333f45769f1eac04112ea727977c316baa..f369c53963e1586a87c4fdbd657a6855c42304fe 100644 (file)
@@ -725,7 +725,6 @@ class AttributeManager(object):
         callable, the callable will only be executed if the given
         `passive` flag is False.
         """
-
         attr = getattr(obj.__class__, key)
         x = attr.get(obj, passive=passive)
         if x is PASSIVE_NORESULT:
index 9ae83460dafdba356d6fd9926841d9f75563aebd..5f60d26e07918011e3bcea16237b2be1baa97a45 100644 (file)
@@ -11,7 +11,7 @@ from sqlalchemy.sql import util as sqlutil
 from sqlalchemy.orm import util as mapperutil
 from sqlalchemy.orm.util import ExtensionCarrier
 from sqlalchemy.orm import sync
-from sqlalchemy.orm.interfaces import MapperProperty, EXT_CONTINUE, SynonymProperty
+from sqlalchemy.orm.interfaces import MapperProperty, EXT_CONTINUE, SynonymProperty, PropComparator
 deferred_load = None
 
 __all__ = ['Mapper', 'class_mapper', 'object_mapper', 'mapper_registry']
@@ -141,23 +141,27 @@ class Mapper(object):
         # was sent to this mapper.
         self.__surrogate_mapper = None
 
-        # whether or not our compile() method has been called already.
-        self.__is_compiled = False
+        self.__props_init = False
 
-        # if this mapper is to be a primary mapper (i.e. the non_primary flag is not set),
-        # associate this Mapper with the given class_ and entity name.  subsequent
-        # calls to class_mapper() for the class_/entity name combination will return this
-        # mapper.
+        self.__should_log_info = logging.is_info_enabled(self.logger)
+        self.__should_log_debug = logging.is_debug_enabled(self.logger)
+        
         self._compile_class()
+        self._compile_extensions()
+        self._compile_inheritance()
+        self._compile_tables()
+        self._compile_properties()
+        self._compile_selectable()
 
-        self.__should_log_debug = logging.is_debug_enabled(self.logger)
         self.__log("constructed")
 
     def __log(self, msg):
-        self.logger.info("(" + self.class_.__name__ + "|" + (self.entity_name is not None and "/%s" % self.entity_name or "") + (self.local_table and self.local_table.name or str(self.local_table)) + (not self._is_primary_mapper() and "|non-primary" or "") + ") " + msg)
+        if self.__should_log_info:
+            self.logger.info("(" + self.class_.__name__ + "|" + (self.entity_name is not None and "/%s" % self.entity_name or "") + (self.local_table and self.local_table.name or str(self.local_table)) + (not self._is_primary_mapper() and "|non-primary" or "") + ") " + msg)
 
     def __log_debug(self, msg):
-        self.logger.debug("(" + self.class_.__name__ + "|" + (self.entity_name is not None and "/%s" % self.entity_name or "") + (self.local_table and self.local_table.name or str(self.local_table)) + (not self._is_primary_mapper() and "|non-primary" or "") + ") " + msg)
+        if self.__should_log_debug:
+            self.logger.debug("(" + self.class_.__name__ + "|" + (self.entity_name is not None and "/%s" % self.entity_name or "") + (self.local_table and self.local_table.name or str(self.local_table)) + (not self._is_primary_mapper() and "|non-primary" or "") + ") " + msg)
 
     def _is_orphan(self, obj):
         optimistic = has_identity(obj)
@@ -179,7 +183,6 @@ class Mapper(object):
 
     def get_property(self, key, resolve_synonyms=False, raiseerr=True):
         """return MapperProperty with the given key."""
-        self.compile()
         prop = self.__props.get(key, None)
         if resolve_synonyms:
             while isinstance(prop, SynonymProperty):
@@ -189,11 +192,12 @@ class Mapper(object):
         return prop
     
     def iterate_properties(self):
-        self.compile()
         return self.__props.itervalues()
     iterate_properties = property(iterate_properties, doc="returns an iterator of all MapperProperty objects.")
     
     def dispose(self):
+        # disaable any attribute-based compilation
+        self.__props_init = True
         attribute_manager.reset_class_managed(self.class_)
         if hasattr(self.class_, 'c'):
             del self.class_.c
@@ -205,65 +209,50 @@ class Mapper(object):
         
     def compile(self):
         """Compile this mapper into its final internal format.
-
-        This is the *external* version of the method which is not
-        reentrant.
         """
 
-        if self.__is_compiled:
+        if self.__props_init:
             return self
         _COMPILE_MUTEX.acquire()
         try:
             # double-check inside mutex
-            if self.__is_compiled:
+            if self.__props_init:
                 return self
-            self._compile_all()
+            # initialize properties on all mappers
+            for mapper in mapper_registry.values():
+                if not mapper.__props_init:
+                    mapper.__initialize_properties()
 
             # if we're not primary, compile us
             if self.non_primary:
-                self._do_compile()
-                self._initialize_properties()
+                self.__initialize_properties()
 
             return self
         finally:
             _COMPILE_MUTEX.release()
 
-    def _compile_all(self):
-        # compile all primary mappers
-        for mapper in mapper_registry.values():
-            if not mapper.__is_compiled:
-                mapper._do_compile()
-
-        # initialize properties on all mappers
-        for mapper in mapper_registry.values():
-            if not mapper.__props_init:
-                mapper._initialize_properties()
-
     def _check_compile(self):
-        if self.non_primary:
-            self._do_compile()
-            self._initialize_properties()
+        if self.non_primary and not self.__props_init:
+            self.__initialize_properties()
         return self
+        
+    def __initialize_properties(self):
+        """Call the ``init()`` method on all ``MapperProperties``
+        attached to this mapper.
 
-    def _do_compile(self):
-        """Compile this mapper into its final internal format.
-
-        This is the *internal* version of the method which is assumed
-        to be called within compile() and is reentrant.
+        This happens after all mappers have completed compiling
+        everything else up until this point, so that all dependencies
+        are fully available.
         """
 
-        if self.__is_compiled:
-            return self
-        self.__log("_do_compile() started")
-        self.__is_compiled = True
-        self.__props_init = False
-        self._compile_extensions()
-        self._compile_inheritance()
-        self._compile_tables()
-        self._compile_properties()
-        self._compile_selectable()
-        self.__log("_do_compile() complete")
-        return self
+        self.__log("_initialize_properties() started")
+        l = [(key, prop) for key, prop in self.__props.iteritems()]
+        for key, prop in l:
+            if getattr(prop, 'key', None) is None:
+                prop.init(key, self)
+        self.__log("_initialize_properties() complete")
+        self.__props_init = True
+
 
     def _compile_extensions(self):
         """Go through the global_extensions list as well as the list
@@ -301,9 +290,9 @@ class Mapper(object):
 
         if self.inherits is not None:
             if isinstance(self.inherits, type):
-                self.inherits = class_mapper(self.inherits, compile=False)._do_compile()
+                self.inherits = class_mapper(self.inherits, compile=False)
             else:
-                self.inherits = self.inherits._do_compile()
+                self.inherits = self.inherits
             if not issubclass(self.class_, self.inherits.class_):
                 raise exceptions.ArgumentError("Class '%s' does not inherit from '%s'" % (self.class_.__name__, self.inherits.class_.__name__))
             if self._is_primary_mapper() != self.inherits._is_primary_mapper():
@@ -537,15 +526,34 @@ class Mapper(object):
                     result.setdefault(fk.column, util.Set()).add(col)
 
         return result
-        
+    
+    class _CompileOnAttr(PropComparator):
+        """placeholder class attribute which fires mapper compilation on access"""
+        def __init__(self, class_, key):
+            self.class_ = class_
+            self.key = key
+        def __getattribute__(self, key):
+            cls = object.__getattribute__(self, 'class_')
+            clskey = object.__getattribute__(self, 'key')
+
+            # get the class' mapper; will compile all mappers
+            class_mapper(cls)
+            
+            if cls.__dict__.get(clskey) is self:
+                # FIXME: there should not be any scenarios where 
+                # a mapper compile leaves this CompileOnAttr in 
+                # place.  
+                warnings.warn(RuntimeWarning("Attribute '%s' on class '%s' was not replaced during mapper compilation operation" % (clskey, cls.__name__)))
+                # clean us up explicitly
+                delattr(cls, clskey)
+                
+            return getattr(getattr(cls, clskey), key)
+            
     def _compile_properties(self):
         """Inspect the properties dictionary sent to the Mapper's
         constructor as well as the mapped_table, and create
         ``MapperProperty`` objects corresponding to each mapped column
         and relation.
-
-        Also grab ``MapperProperties`` from the inherited mapper, if
-        any, and create copies of them to attach to this Mapper.
         """
 
         # object attribute names mapped to MapperProperty objects
@@ -575,7 +583,7 @@ class Mapper(object):
                 self.columns[column.key] = self.select_table.corresponding_column(column, keys_ok=True, raiseerr=True)
 
             column_key = (self.column_prefix or '') + column.key
-            prop = self.__props.get(column.key, None)
+            prop = self.__props.get(column_key, None)
 
             if prop is None:
                 if (self.include_properties is not None and
@@ -590,6 +598,11 @@ class Mapper(object):
                 
                 prop = ColumnProperty(column)
                 self.__props[column_key] = prop
+
+                # TODO: centralize _CompileOnAttr logic, move into MapperProperty classes
+                if not hasattr(self.class_, column_key) and not self.non_primary:
+                    setattr(self.class_, column_key, Mapper._CompileOnAttr(self.class_, column_key))
+                
                 prop.set_parent(self)
                 self.__log("adding ColumnProperty %s" % (column_key))
             elif isinstance(prop, ColumnProperty):
@@ -612,23 +625,6 @@ class Mapper(object):
             self.columntoproperty.setdefault(column, []).append(prop)
 
 
-    def _initialize_properties(self):
-        """Call the ``init()`` method on all ``MapperProperties``
-        attached to this mapper.
-
-        This happens after all mappers have completed compiling
-        everything else up until this point, so that all dependencies
-        are fully available.
-        """
-
-        self.__log("_initialize_properties() started")
-        l = [(key, prop) for key, prop in self.__props.iteritems()]
-        for key, prop in l:
-            if getattr(prop, 'key', None) is None:
-                prop.init(key, self)
-        self.__log("_initialize_properties() complete")
-        self.__props_init = True
-
     def _compile_selectable(self):
         """If the 'select_table' keyword argument was specified, set
         up a second *surrogate mapper* that will be used for select
@@ -762,10 +758,7 @@ class Mapper(object):
         """
 
         self.properties[key] = prop
-        if self.__is_compiled:
-            # if we're compiled, make sure all the other mappers are compiled too
-            self._compile_all()
-            self._compile_property(key, prop, init=True)
+        self._compile_property(key, prop, init=self.__props_init)
 
     def _create_prop_from_column(self, column):
         column = util.to_list(column)
@@ -805,9 +798,15 @@ class Mapper(object):
             prop = col
 
         self.__props[key] = prop
+        
+
         if setparent:
             prop.set_parent(self)
 
+            # TODO: centralize _CompileOnAttr logic, move into MapperProperty classes
+            if (not isinstance(prop, SynonymProperty) or prop.proxy) and not self.non_primary and not hasattr(self.class_, key):
+                setattr(self.class_, key, Mapper._CompileOnAttr(self.class_, key))
+
         if isinstance(prop, ColumnProperty):
             # relate the mapper's "select table" to the given ColumnProperty
             col = self.select_table.corresponding_column(prop.columns[0], keys_ok=True, raiseerr=False)
@@ -831,6 +830,7 @@ class Mapper(object):
 
     def _is_primary_mapper(self):
         """Return True if this mapper is the primary mapper for its class key (class + entity_name)."""
+        # FIXME: cant we just look at "non_primary" flag ?
         return mapper_registry.get(self.class_key, None) is self
 
     def primary_mapper(self):
@@ -864,7 +864,6 @@ class Mapper(object):
         from the extension chain.
         """
 
-        self.compile()
         s = self.extension.get_session()
         if s is EXT_CONTINUE:
             raise exceptions.InvalidRequestError("No contextual Session is established.  Use a MapperExtension that implements get_session or use 'import sqlalchemy.mods.threadlocal' to establish a default thread-local contextual session.")
@@ -1581,9 +1580,7 @@ class ClassKey(object):
     def __repr__(self):
         return "ClassKey(%s, %s)" % (repr(self.class_), repr(self.entity_name))
 
-    def dispose(self):
-        type(self).dispose_static(self.class_, self.entity_name)
-
+    
 def has_identity(object):
     return hasattr(object, '_instance_key')
 
index a13923885fffcc5c057e0e60ad4b965c0a661e0a..d38c5cf4a6056d2bce80a53ec81845696b9d1ba8 100644 (file)
@@ -6,18 +6,6 @@
 
 """Topological sorting algorithms.
 
-The key to the unit of work is to assemble a list of dependencies
-amongst all the different mappers that have been defined for classes.
-
-Related tables with foreign key constraints have a definite insert
-order, deletion order, objects need dependent properties from parent
-objects set up before saved, etc.
-
-These are all encoded as dependencies, in the form *mapper X is
-dependent on mapper Y*, meaning mapper Y's objects must be saved
-before those of mapper X, and mapper X's objects must be deleted
-before those of mapper Y.
-
 The topological sort is an algorithm that receives this list of
 dependencies as a *partial ordering*, that is a list of pairs which
 might say, *X is dependent on Y*, *Q is dependent on Z*, but does not
@@ -28,18 +16,6 @@ then only towards just some of the other elements.  For a particular
 partial ordering, there can be many possible sorts that satisfy the
 conditions.
 
-An intrinsic *gotcha* to this algorithm is that since there are many
-possible outcomes to sorting a partial ordering, the algorithm can
-return any number of different results for the same input; just
-running it on a different machine architecture, or just random
-differences in the ordering of dictionaries, can change the result
-that is returned.  While this result is guaranteed to be true to the
-incoming partial ordering, if the partial ordering itself does not
-properly represent the dependencies, code that works fine will
-suddenly break, then work again, then break, etc.  Most of the bugs
-I've chased down while developing the *unit of work* have been of this
-nature - very tricky to reproduce and track down, particularly before
-I realized this characteristic of the algorithm.
 """
 
 from sqlalchemy import util
@@ -158,8 +134,9 @@ class QueueDependencySorter(object):
     """Topological sort adapted from wikipedia's article on the subject.
 
     It creates a straight-line list of elements, then a second pass
-    groups non-dependent actions together to build more of a tree
-    structure with siblings.
+    batches non-dependent elements as siblings in a tree structure.  Future
+    versions of this algorithm may separate the "convert to a tree" 
+    step.
     """
 
     def __init__(self, tuples, allitems):
index 321540419f094e7a9104c02f0f1eb86d344d7eee..633e6b0c1a9f2d22f0c2376c88dcf978503207ab 100644 (file)
@@ -122,10 +122,11 @@ def hash(string):
 class ArgSingleton(type):
     instances = {}
 
-    def dispose_static(self, *args):
-        hashkey = (self, args)
-        #if hashkey in ArgSingleton.instances:
-        del ArgSingleton.instances[hashkey]
+    def dispose(cls):
+        for key in ArgSingleton.instances:
+            if key[0] is cls:
+                del ArgSingleton.instances[key]
+    dispose = classmethod(dispose)
 
     def __call__(self, *args):
         hashkey = (self, args)
index 870f3e86922cee932ceb42ad5ea63f4110d75b23..fbdb4019e1fb9cd7834f6a01f409466e6f5145d5 100644 (file)
@@ -170,14 +170,10 @@ class ConstructionTest(ORMTest):
         class Product(Content): pass
 
         content_types = mapper(ContentType, content_type)
-        contents = mapper(Content, content, properties={
-            'content_type':relation(content_types)
-        }, polymorphic_identity='contents')
-
-        products = mapper(Product, product, inherits=contents, polymorphic_identity='products')
-        
         try:
-            compile_mappers()
+            contents = mapper(Content, content, properties={
+                'content_type':relation(content_types)
+            }, polymorphic_identity='contents')
             assert False
         except exceptions.ArgumentError, e:
             assert str(e) == "Mapper 'Mapper|Content|content' specifies a polymorphic_identity of 'contents', but no mapper in it's hierarchy specifies the 'polymorphic_on' column argument"
@@ -377,8 +373,8 @@ class DistinctPKTest(ORMTest):
 
     def test_explicit_composite_pk(self):
         person_mapper = mapper(Person, person_table)
-        mapper(Employee, employee_table, inherits=person_mapper, primary_key=[person_table.c.id, employee_table.c.id])
         try:
+            mapper(Employee, employee_table, inherits=person_mapper, primary_key=[person_table.c.id, employee_table.c.id])
             self._do_test(True)
             assert False
         except RuntimeWarning, e:
index caee34b09dcc78a868617817563f10719e90ef3f..f067b330bd81e26b096dd604351cd69d5698c0a2 100644 (file)
@@ -90,7 +90,7 @@ class CompileTest(PolymorphTest):
         session.flush()
         print session.query(Engineer).select()
 
-        print session.query(Person).select()        
+        print session.query(Person).select()
     
     def testcompile2(self):
         """test that a mapper can reference a property whose mapper inherits from this one."""
index 6e03c97e95ce9bfd5dd5ee2218d396945d16147c..a58800c56bd2604d2564ecfbaa5bf436afdf8a9e 100644 (file)
@@ -237,7 +237,6 @@ def generate_test(jointype="join1", usedata=False):
         if usedata:
             mapper(Data, data)
     
-        mapper(Manager, managers, inherits=Person, inherit_condition=people.c.person_id==managers.c.person_id, polymorphic_identity='manager')
         if usedata:
             mapper(Person, people, select_table=poly_union, polymorphic_identity='person', polymorphic_on=people.c.type,
                   properties={
@@ -253,6 +252,8 @@ def generate_test(jointype="join1", usedata=False):
                     }        
             )
 
+        mapper(Manager, managers, inherits=Person, inherit_condition=people.c.person_id==managers.c.person_id, polymorphic_identity='manager')
+
         sess = create_session()
         p = Person(name='person1')
         p2 = Person(name='person2')
index 2459cd36e1f58fb2dfb3dedb416d4673968e0e47..5c8c64bed47c408cb5f96c9a1d2bce5e3109bf8d 100644 (file)
@@ -272,12 +272,6 @@ class InheritTest(ORMTest):
                 )
             )
 
-        detail_mapper = mapper(Detail, inherits=Product,
-            polymorphic_identity='detail')
-
-        raster_document_mapper = mapper(RasterDocument, inherits=Document,
-            polymorphic_identity='raster_document')
-
         product_mapper = mapper(Product, products_table,
             polymorphic_on=products_table.c.product_type,
             polymorphic_identity='product', properties={
@@ -285,8 +279,8 @@ class InheritTest(ORMTest):
                     backref='product', cascade='all, delete-orphan'),
             })
 
-        assembly_mapper = mapper(Assembly, inherits=Product,
-            polymorphic_identity='assembly')
+        detail_mapper = mapper(Detail, inherits=Product,
+            polymorphic_identity='detail')
 
         document_mapper = mapper(Document, documents_table,
             polymorphic_on=documents_table.c.document_type,
@@ -297,6 +291,12 @@ class InheritTest(ORMTest):
                 ),
             )
 
+        raster_document_mapper = mapper(RasterDocument, inherits=Document,
+            polymorphic_identity='raster_document')
+
+        assembly_mapper = mapper(Assembly, inherits=Product,
+            polymorphic_identity='assembly')
+
         session = create_session()
 
         a1 = Assembly(name='a1')
index 905ad5bfe8e075bff1d524df2ca117b5029d1702..f00c1c59d0b12f3db4b9c9678aed0a31dda6d3d2 100644 (file)
@@ -212,14 +212,7 @@ class MapperTest(MapperSuperTest):
         m1 = mapper(Address, addresses)
 
         m2 = mapper(User, users, properties = dict(addresses=relation(Address,private=True,lazy=False)) )
-        assert m1._Mapper__is_compiled is False
-        assert m2._Mapper__is_compiled is False
-        
-#        compile_mappers()
-        print "NEW USER"
         u=User()
-        print "NEW USER DONE"
-        assert m2._Mapper__is_compiled is True
         u.user_name='Justin'
         a = Address()
         a.address_id=17  # to work around the hardcoded IDs in this test suite....
@@ -236,12 +229,30 @@ class MapperTest(MapperSuperTest):
         s.refresh(u) #hangs
         
     def testprops(self):
-        """tests the various attributes of the properties attached to classes"""
         m = mapper(User, users, properties = {
             'addresses' : relation(mapper(Address, addresses))
         }).compile()
         self.assert_(User.addresses.property is m.get_property('addresses'))
+    
+    def testcompileonprop(self):
+        mapper(User, users, properties = {
+            'addresses' : relation(mapper(Address, addresses))
+        })
+        User.addresses.any(Address.email_address=='foo@bar.com')
+        clear_mappers()
 
+        mapper(User, users, properties = {
+            'addresses' : relation(mapper(Address, addresses))
+        })
+        assert (User.user_id==3).compare(users.c.user_id==3)
+
+        clear_mappers()
+
+        class Foo(User):pass
+        mapper(User, users)
+        mapper(Foo, addresses, inherits=User)
+        assert getattr(Foo().__class__, 'user_name').get is not None
+        
     def testpropfilters(self):
         t = Table('person', MetaData(),
                   Column('id', Integer, primary_key=True),
@@ -276,12 +287,13 @@ class MapperTest(MapperSuperTest):
                      column_prefix="p_")
 
         p_m.compile()
+        #compile_mappers()
         
         def assert_props(cls, want):
             have = set([n for n in dir(cls) if not n.startswith('_')])
             want = set(want)
             want.add('c')
-            self.assert_(have == want)
+            self.assert_(have == want, repr(have) + " " + repr(want))
 
         assert_props(Person, ['id', 'name', 'type'])
         assert_props(Employee, ['boss', 'boss_id', 'employee_number',
@@ -440,6 +452,9 @@ class MapperTest(MapperSuperTest):
             adname = synonym('addresses')
         ))
         
+        assert hasattr(User, 'adlist')
+        assert not hasattr(User, 'adname')
+        
         u = sess.query(User).get_by(uname='jack')
         self.assert_result(u.adlist, Address, *(user_address_result[0]['addresses'][1]))
 
index 6dd4a08e83b0a7e94cf7de45f6e3e5045a84a915..63456b638573effad8d8fcff49f0e1dc183b73f6 100644 (file)
@@ -464,7 +464,7 @@ class ForeignPKTest(ORMTest):
                       'sites' : relation(PersonSite), 
               },
             )
-
+        compile_mappers()
         assert list(m2.get_property('sites').foreign_keys) == [peoplesites.c.person]
         p = Person()
         p.person = 'im the key'