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

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

Adiciona suporte a versionamento do banco de dados baseado no exemplo de versioning do sqlalchemy.

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