source: publico/il.spdo/trunk/il/spdo/history_meta.py @ 5453

Última Alteração nesse arquivo desde 5453 foi 5453, incluída por fabianosantos, 9 anos atrás

Criado session extension do sqlalchemy que junta as funcionalidades do mecanismo de transacao do zope com o controle de versao das tabelas do banco de dados. Isso ficou um espetaculo e inacreditavelmente simples.

File size: 6.6 KB
Linha 
1# -*- coding: utf-8 -*-
2
3# Baseado em https://bitbucket.org/sqlalchemy/sqlalchemy/src/de4eb56fb6c8/examples/versioning/history_meta.py
4# Mais informações em http://www.sqlalchemy.org/docs/orm/examples.html?highlight=version#versioned-objects
5
6from sqlalchemy.ext.declarative import DeclarativeMeta
7from sqlalchemy.orm import mapper, class_mapper, attributes, object_mapper
8from sqlalchemy.orm.exc import UnmappedClassError, UnmappedColumnError
9from sqlalchemy import Table, Column, ForeignKeyConstraint, Integer
10from sqlalchemy.orm.interfaces import SessionExtension
11from sqlalchemy.orm.properties import RelationshipProperty
12from zope.sqlalchemy.datamanager import ZopeTransactionExtension
13
14def col_references_table(col, table):
15    for fk in col.foreign_keys:
16        if fk.references(table):
17            return True
18    return False
19
20def _history_mapper(local_mapper):
21    cls = local_mapper.class_
22
23    # set the "active_history" flag
24    # on on column-mapped attributes so that the old version
25    # of the info is always loaded (currently sets it on all attributes)
26    for prop in local_mapper.iterate_properties:
27        getattr(local_mapper.class_, prop.key).impl.active_history = True
28
29    super_mapper = local_mapper.inherits
30    super_history_mapper = getattr(cls, '__history_mapper__', None)
31
32    polymorphic_on = None
33    super_fks = []
34    if not super_mapper or local_mapper.local_table is not super_mapper.local_table:
35        cols = []
36        for column in local_mapper.local_table.c:
37            if column.name == 'version':
38                continue
39
40            col = column.copy()
41            col.unique = False
42
43            if super_mapper and col_references_table(column, super_mapper.local_table):
44                super_fks.append((col.key, list(super_history_mapper.local_table.primary_key)[0]))
45
46            cols.append(col)
47
48            if column is local_mapper.polymorphic_on:
49                polymorphic_on = col
50
51        if super_mapper:
52            super_fks.append(('version', super_history_mapper.base_mapper.local_table.c.version))
53            cols.append(Column('version', Integer, primary_key=True))
54        else:
55            cols.append(Column('version', Integer, primary_key=True))
56
57        if super_fks:
58            cols.append(ForeignKeyConstraint(*zip(*super_fks)))
59
60        table = Table(local_mapper.local_table.name + '_history', local_mapper.local_table.metadata,
61           *cols
62        )
63    else:
64        # single table inheritance.  take any additional columns that may have
65        # been added and add them to the history table.
66        for column in local_mapper.local_table.c:
67            if column.key not in super_history_mapper.local_table.c:
68                col = column.copy()
69                col.unique = False
70                super_history_mapper.local_table.append_column(col)
71        table = None
72
73    if super_history_mapper:
74        bases = (super_history_mapper.class_,)
75    else:
76        bases = local_mapper.base_mapper.class_.__bases__
77    versioned_cls = type.__new__(type, "%sHistory" % cls.__name__, bases, {})
78
79    m = mapper(
80            versioned_cls,
81            table,
82            inherits=super_history_mapper,
83            polymorphic_on=polymorphic_on,
84            polymorphic_identity=local_mapper.polymorphic_identity
85            )
86    cls.__history_mapper__ = m
87
88    if not super_history_mapper:
89        cls.version = Column('version', Integer, default=1, nullable=False)
90
91
92class VersionedMeta(DeclarativeMeta):
93    def __init__(cls, classname, bases, dict_):
94        DeclarativeMeta.__init__(cls, classname, bases, dict_)
95
96        try:
97            mapper = class_mapper(cls)
98            _history_mapper(mapper)
99        except UnmappedClassError:
100            pass
101
102
103def versioned_objects(iter):
104    for obj in iter:
105        if hasattr(obj, '__history_mapper__'):
106            yield obj
107
108def create_version(obj, session, deleted = False):
109    obj_mapper = object_mapper(obj)
110    history_mapper = obj.__history_mapper__
111    history_cls = history_mapper.class_
112
113    obj_state = attributes.instance_state(obj)
114
115    attr = {}
116
117    obj_changed = False
118
119    for om, hm in zip(obj_mapper.iterate_to_root(), history_mapper.iterate_to_root()):
120        if hm.single:
121            continue
122
123        for hist_col in hm.local_table.c:
124            if hist_col.key == 'version':
125                continue
126
127            obj_col = om.local_table.c[hist_col.key]
128
129            # get the value of the
130            # attribute based on the MapperProperty related to the
131            # mapped column.  this will allow usage of MapperProperties
132            # that have a different keyname than that of the mapped column.
133            try:
134                prop = obj_mapper.get_property_by_column(obj_col)
135            except UnmappedColumnError:
136                # in the case of single table inheritance, there may be
137                # columns on the mapped table intended for the subclass only.
138                # the "unmapped" status of the subclass column on the
139                # base class is a feature of the declarative module as of sqla 0.5.2.
140                continue
141
142            # expired object attributes and also deferred cols might not be in the
143            # dict.  force it to load no matter what by using getattr().
144            if prop.key not in obj_state.dict:
145                getattr(obj, prop.key)
146
147            a, u, d = attributes.get_history(obj, prop.key)
148
149            if d:
150                attr[hist_col.key] = d[0]
151                obj_changed = True
152            elif u:
153                attr[hist_col.key] = u[0]
154            else:
155                # if the attribute had no value.
156                attr[hist_col.key] = a[0]
157                obj_changed = True
158
159    if not obj_changed:
160        # not changed, but we have relationships.  OK
161        # check those too
162        for prop in obj_mapper.iterate_properties:
163            if isinstance(prop, RelationshipProperty) and \
164                attributes.get_history(obj, prop.key).has_changes():
165                obj_changed = True
166                break
167
168    if not obj_changed and not deleted:
169        return
170
171    attr['version'] = obj.version
172    hist = history_cls()
173    for key, value in attr.iteritems():
174        setattr(hist, key, value)
175    session.add(hist)
176    obj.version += 1
177
178class VersionedListener(SessionExtension):
179    def before_flush(self, session, flush_context, instances):
180        for obj in versioned_objects(session.dirty):
181            create_version(obj, session)
182        for obj in versioned_objects(session.deleted):
183            create_version(obj, session, deleted = True)
184
185class ZopeVersionedExtension(ZopeTransactionExtension, VersionedListener):
186    pass
187
Note: Veja TracBrowser para ajuda no uso do navegador do trac.
 

The contents and data of this website are published under license:
Creative Commons 4.0 Brasil - Atribuir Fonte - Compartilhar Igual.