possible rearrangement of history_meta based on events

Issue #2313 resolved
Mike Bayer repo owner created an issue

which has to be a new event too...

diff -r 5d6376fbd5ca4103a26118a6fffd1e95be0d5161 examples/versioning/history_meta.py
--- a/examples/versioning/history_meta.py   Fri Oct 28 17:46:28 2011 -0400
+++ b/examples/versioning/history_meta.py   Fri Oct 28 18:46:27 2011 -0400
@@ -1,7 +1,7 @@
 from sqlalchemy.ext.declarative import DeclarativeMeta
 from sqlalchemy.orm import mapper, class_mapper, attributes, object_mapper
 from sqlalchemy.orm.exc import UnmappedClassError, UnmappedColumnError
-from sqlalchemy import Table, Column, ForeignKeyConstraint, Integer
+from sqlalchemy import Table, Column, ForeignKeyConstraint, Integer, event
 from sqlalchemy.orm.interfaces import SessionExtension
 from sqlalchemy.orm.properties import RelationshipProperty

@@ -13,6 +13,9 @@

 def _history_mapper(local_mapper):
     cls = local_mapper.class_
+    if '__history_mapper__' in cls.__dict__ or \
+        hasattr(cls, '_is_history'):
+        return

     # set the "active_history" flag
     # on on column-mapped attributes so that the old version
@@ -25,6 +28,7 @@

     polymorphic_on = None
     super_fks = [    if not super_mapper or local_mapper.local_table is not super_mapper.local_table:
         cols = [](]
+
)
         for column in local_mapper.local_table.c:
@@ -68,7 +72,7 @@
         bases = (super_history_mapper.class_,)
     else:
         bases = local_mapper.base_mapper.class_.__bases__
-    versioned_cls = type.__new__(type, "%sHistory" % cls.__name__, bases, {})
+    versioned_cls = type.__new__(type, "%sHistory" % cls.__name__, bases, {'_is_history':True})

     m = mapper(
             versioned_cls, 
@@ -80,19 +84,19 @@
     cls.__history_mapper__ = m

     if not super_history_mapper:
-        cls.version = Column('version', Integer, default=1, nullable=False)
+        col = Column('version', Integer, default=1, nullable=False)
+        local_mapper.local_table.append_column(col)
+        local_mapper.add_property('version', col)


 class VersionedMeta(DeclarativeMeta):
-    def __init__(cls, classname, bases, dict_):
-        DeclarativeMeta.__init__(cls, classname, bases, dict_)
+    pass

-        try:
-            mapper = class_mapper(cls)
-            _history_mapper(mapper)
-        except UnmappedClassError:
-            pass

+@event.listens_for(mapper, "mapper_constructed")
+def setup_history_mapper(mapper, cls):
+    if isinstance(cls, VersionedMeta):
+        _history_mapper(mapper)

 def versioned_objects(iter):
     for obj in iter:
diff -r 5d6376fbd5ca4103a26118a6fffd1e95be0d5161 lib/sqlalchemy/orm/events.py
--- a/lib/sqlalchemy/orm/events.py  Fri Oct 28 17:46:28 2011 -0400
+++ b/lib/sqlalchemy/orm/events.py  Fri Oct 28 18:46:27 2011 -0400
@@ -385,6 +385,21 @@

         """

+    def mapper_constructed(self, mapper, class_):
+        """Called when the mapper is finished constructed, but not yet
+        configured.
+        
+        Events that wish to create new mappers as a result
+        of a previous mapping should go here, as the mapper is 
+        mostly ready, but we aren't out of the "we've compiled all 
+        the mappers" phase.
+
+        :param mapper: the :class:`.Mapper` which is the target
+         of this event.
+        :param class\_: the mapped class.
+        
+        """
+
     def mapper_configured(self, mapper, class_):
         """Called when the mapper for the class is fully configured.

diff -r 5d6376fbd5ca4103a26118a6fffd1e95be0d5161 lib/sqlalchemy/orm/mapper.py
--- a/lib/sqlalchemy/orm/mapper.py  Fri Oct 28 17:46:28 2011 -0400
+++ b/lib/sqlalchemy/orm/mapper.py  Fri Oct 28 18:46:27 2011 -0400
@@ -207,6 +207,7 @@
             _new_mappers = True
             self._log("constructed")
             self._expire_memoizations()
+            self.dispatch.mapper_constructed(self, self.class_)
         finally:
             _COMPILE_MUTEX.release()

@@ -2798,7 +2799,12 @@
                             mapper._configure_failed = exc
                         raise

+            # TODO: if a "mapper configured" event
+            # creates a new mapper, then this
+            # is wrong.  however, a simple move
+            # up above is failing some tests.
             _new_mappers = False
+
         finally:
             _already_compiling = False
     finally:

we'd want to test this against AbstractConcreteBase.

Comments (3)

  1. Log in to comment