Changeset 358

Show
Ignore:
Timestamp:
07/11/08 10:55:58 (5 years ago)
Author:
ged
Message:
  • added add_mapper_extension helper method on EntityBuilder
  • minor cleanups in versioned ext
Location:
elixir/trunk/elixir
Files:
2 modified

Legend:

Unmodified
Added
Removed
  • elixir/trunk/elixir/ext/versioned.py

    r336 r358  
    22A versioning plugin for Elixir. 
    33 
    4 Entities that are marked as versioned with the `acts_as_versioned` statement  
     4Entities that are marked as versioned with the `acts_as_versioned` statement 
    55will automatically have a history table created and a timestamp and version 
    6 column added to their tables. In addition, versioned entities are provided  
    7 with four new methods: revert, revert_to, compare_with and get_as_of, and one  
     6column added to their tables. In addition, versioned entities are provided 
     7with four new methods: revert, revert_to, compare_with and get_as_of, and one 
    88new attribute: versions.  Entities with compound primary keys are supported. 
    99 
     
    3232 
    3333Also included in the module is a `after_revert` decorator that can be used to 
    34 decorate methods on the versioned entity that will be called following that  
     34decorate methods on the versioned entity that will be called following that 
    3535instance being reverted. 
    3636 
    37 The acts_as_versioned statement also accepts an optional `ignore` argument  
    38 that consists of a list of strings, specifying names of fields.  Changes in  
     37The acts_as_versioned statement also accepts an optional `ignore` argument 
     38that consists of a list of strings, specifying names of fields.  Changes in 
    3939those fields will not result in a version increment.  In addition, you can 
    4040pass in an optional `check_concurrent` argument, which will use SQLAlchemy's 
    41 built-in optimistic concurrency mechanisms.  
     41built-in optimistic concurrency mechanisms. 
    4242 
    4343Note that relationships that are stored in mapping tables will not be included 
     
    5555from elixir                import Integer, DateTime 
    5656from elixir.statements     import Statement 
     57from elixir.properties     import EntityBuilder 
    5758 
    5859__all__ = ['acts_as_versioned', 'after_revert'] 
     
    9091        instance.timestamp = datetime.now() 
    9192        return EXT_CONTINUE 
    92              
     93 
    9394    def before_update(self, mapper, connection, instance): 
    94         values = instance.table.select(get_entity_where(instance)).execute().fetchone()  
     95        old_values = instance.table.select(get_entity_where(instance)) \ 
     96                                   .execute().fetchone() 
    9597 
    9698        # SA might've flagged this for an update even though it didn't change. 
     
    103105            if key in ignored: 
    104106                continue 
    105             if getattr(instance, key) != values[key]: 
     107            if getattr(instance, key) != old_values[key]: 
    106108                # the instance was really updated, so we create a new version 
    107                 colvalues = dict(values.items())  
    108                 connection.execute(instance.__class__.__history_table__.insert(), colvalues) 
     109                dict_values = dict(old_values.items()) 
     110                connection.execute( 
     111                    instance.__class__.__history_table__.insert(), dict_values) 
    109112                instance.version = instance.version + 1 
    110113                instance.timestamp = datetime.now() 
     
    112115 
    113116        return EXT_CONTINUE 
    114          
     117 
    115118    def before_delete(self, mapper, connection, instance): 
    116119        connection.execute(instance.__history_table__.delete( 
     
    127130# 
    128131 
    129 class VersionedEntityBuilder(object): 
    130          
     132class VersionedEntityBuilder(EntityBuilder): 
     133 
    131134    def __init__(self, entity, ignore=[], check_concurrent=False): 
    132         entity._descriptor.add_mapper_extension(versioned_mapper_extension) 
    133135        self.entity = entity 
     136        self.add_mapper_extension(versioned_mapper_extension) 
     137        #TODO: we should rather check that the version_id_col isn't set  
     138        # externally 
    134139        self.check_concurrent = check_concurrent 
    135          
     140 
    136141        # Changes in these fields will be ignored 
     142        ignore.extend(['version', 'timestamp']) 
    137143        entity.__ignored_fields__ = ignore 
    138         entity.__ignored_fields__.extend(['version', 'timestamp']) 
    139          
     144 
    140145    def create_non_pk_cols(self): 
    141146        # add a version column to the entity, along with a timestamp 
    142         version_col = Column('version', Integer) 
    143         timestamp_col = Column('timestamp', DateTime) 
    144         self.entity._descriptor.add_column(version_col) 
    145         self.entity._descriptor.add_column(timestamp_col) 
    146          
     147        self.add_table_column(Column('version', Integer)) 
     148        self.add_table_column(Column('timestamp', DateTime)) 
     149 
    147150        # add a concurrent_version column to the entity, if required 
    148151        if self.check_concurrent: 
    149152            self.entity._descriptor.version_id_col = 'concurrent_version' 
    150     
     153 
    151154    # we copy columns from the main entity table, so we need it to exist first 
    152155    def after_table(self): 
    153156        entity = self.entity 
    154          
     157 
    155158        # look for events 
    156159        after_revert_events = [] 
     
    158161            if getattr(func, '_elixir_after_revert', False): 
    159162                after_revert_events.append(func) 
    160          
     163 
    161164        # create a history table for the entity 
    162165        #TODO: fail more noticeably in case there is a version col 
    163166        columns = [ 
    164             column.copy() for column in entity.table.c  
     167            column.copy() for column in entity.table.c 
    165168            if column.name not in ('version', 'concurrent_version') 
    166169        ] 
    167170        columns.append(Column('version', Integer, primary_key=True)) 
    168         table = Table(entity.table.name + '_history', entity.table.metadata,  
     171        table = Table(entity.table.name + '_history', entity.table.metadata, 
    169172            *columns 
    170173        ) 
    171174        entity.__history_table__ = table 
    172          
     175 
    173176        # create an object that represents a version of this entity 
    174177        class Version(object): 
    175178            pass 
    176              
     179 
    177180        # map the version class to the history table for this entity 
    178181        Version.__name__ = entity.__name__ + 'Version' 
    179182        Version.__versioned_entity__ = entity 
    180183        mapper(Version, entity.__history_table__) 
    181                          
     184 
    182185        # attach utility methods and properties to the entity 
    183186        def get_versions(self): 
     
    190193            v.append(self) 
    191194            return v 
    192          
     195 
    193196        def get_as_of(self, dt): 
    194197            # if the passed in timestamp is older than our current version's 
     
    196199            if self.timestamp < dt: 
    197200                return self 
    198              
     201 
    199202            # otherwise, we need to look to the history table to get our 
    200203            # older version 
    201             query = object_session(self).query(Version) 
    202             query = query.filter(and_(get_history_where(self),  
    203                                       Version.timestamp <= dt)) 
    204             query = query.order_by(desc(Version.timestamp)).limit(1) 
     204            sess = object_session(self) 
     205            query = sess.query(Version) \ 
     206                        .filter(and_(get_history_where(self), 
     207                                     Version.timestamp <= dt)) \ 
     208                        .order_by(desc(Version.timestamp)).limit(1) 
    205209            return query.first() 
    206          
     210 
    207211        def revert_to(self, to_version): 
    208212            if isinstance(to_version, Version): 
    209213                to_version = to_version.version 
    210                  
     214 
    211215            hist = entity.__history_table__ 
    212216            old_version = hist.select(and_( 
    213                 get_history_where(self),  
     217                get_history_where(self), 
    214218                hist.c.version == to_version 
    215219            )).execute().fetchone() 
    216              
     220 
    217221            entity.table.update(get_entity_where(self)).execute( 
    218222                dict(old_version.items()) 
    219223            ) 
    220              
    221             hist.delete(and_(get_history_where(self),  
     224 
     225            hist.delete(and_(get_history_where(self), 
    222226                             hist.c.version >= to_version)).execute() 
    223227            self.expire() 
    224             for event in after_revert_events:  
     228            for event in after_revert_events: 
    225229                event(self) 
    226          
     230 
    227231        def revert(self): 
    228232            assert self.version > 1 
    229233            self.revert_to(self.version - 1) 
    230              
     234 
    231235        def compare_with(self, version): 
    232236            differences = {} 
     
    239243                    differences[column.name] = (this, that) 
    240244            return differences 
    241          
     245 
    242246        entity.versions = property(get_versions) 
    243247        entity.get_as_of = get_as_of 
  • elixir/trunk/elixir/properties.py

    r350 r358  
    7878 
    7979    # helper methods 
     80    def add_table_column(self, column): 
     81        self.entity._descriptor.add_column(column) 
     82 
    8083    def add_mapper_property(self, name, prop): 
    8184        self.entity._descriptor.add_property(name, prop) 
    8285 
    83     def add_table_column(self, column): 
    84         self.entity._descriptor.add_column(column) 
     86    def add_mapper_extension(self, ext): 
     87        self.entity._descriptor.add_mapper_extension(ext) 
    8588 
    8689