Rufus Pollock avatar Rufus Pollock committed 4dcf50c

[sqlalchemy][s]: ([m] in effort) get m2m history working.

Comments (0)

Files changed (2)

vdm/sqlalchemy/model.py

     cls.__revisioned_attributes__ = [ col.key for col in cols ]
     return cols
 
+## TODO: address worry that iterator over columns may mean we get pkids in
+## different order ...
 def get_object_id(obj):
     obj_mapper = object_mapper(obj)
     object_id = [obj.__class__.__name__]
     object_id = tuple(object_id)
     return object_id
 
-# Questions: when does this create the first version
 
 def create_version(obj, session,
         operation_type=ChangeObject.OperationType.UPDATE
                 attr[col_key] = a[0]
                 obj_changed = True
 
-    if not obj_changed:
-        # not changed, but we have relationships.  OK
-        # check those too
-        # Why bother with this?
-        for prop in obj_mapper.iterate_properties:
-            if isinstance(prop, RelationshipProperty) and \
-                attributes.get_history(obj, prop.key).has_changes():
+    # not changed, but we have relationships.  OK
+    # check those too and include values into our attributes
+    for prop in obj_mapper.iterate_properties:
+        if isinstance(prop, RelationshipProperty):
+            a,u,d = attributes.get_history(obj, prop.key)
+            if d or a:
                 obj_changed = True
-                break
+            attr[prop.key] = [get_object_id(x) for x in getattr(obj, prop.key)
+                    or [] ]
 
     if not obj_changed and operation_type == ChangeObject.OperationType.UPDATE:
         return
 
-    ## TODO: address worry that iterator over columns may mean we get pkids in
-    ## different order ...
     object_id = get_object_id(obj)
 
     # HACK (sort of)
 
 
 class VersionedListener(SessionExtension):
-    '''
+    '''Session extension that does versioning/revisioning.
 
     Notes
     =====
     that are updated or deleted and hence object pks are set (i.e. it does not
     do anything for creation)
 
-    NB: cannot use before_commit either (works for update and delete but not
-    create ...)
-
     TODO: is there a danger here that things will not work if we are not using
     commit (and only flush).
     '''
                 operation_type=ChangeObject.OperationType.CREATE
                 )
 
+    # rather inefficient to do both but solves weird bug whereby if one does
+    # change object
+    # session.flush()
+    # change an object
+    # session.commit()
+    # we were failing to get changeobject updated
     def before_commit(self, session):
         for obj in versioned_objects(session.dirty):
             create_version(obj, session)

vdm/test/sqlalchemy/test_demo.py

         print p2_changeobjects
         assert optype == ChangeObject.OperationType.DELETE, optype
 
-    def test_09_versioning(self):
+    def test_08_versioning(self):
         p1 = Session.query(Package).filter_by(name=self.name1).one()
         changeobjects = all_revisions(p1)
         co = changeobjects[-1]
         assert co.data['name'] == self.name1
         assert co.data['title'] == self.title1, co.data
 
-#    def test_12_versioning_m2m_1(self):
-#        p1 = Session.query(Package).filter_by(name=self.name1).one()
-#        rev1 = Session.query(Revision).get(self.rev1_id)
-#        ptag = p1.package_tags[0]
-#        # does not exist
-#        assert ptag.get_as_of(rev1) == None
-#
-#    def test_13_versioning_m2m(self):
-#        p1 = Session.query(Package).filter_by(name=self.name1).one()
-#        rev1 = Session.query(Revision).get(self.rev1_id)
-#        p1r1 = p1.get_as_of(rev1)
-#        assert len(p1.tags_active) == 0
-#        # NB: deleted includes tags that were non-existent
-#        assert len(p1.tags_deleted) == 1
-#        assert len(p1.tags) == 0
-#        assert len(p1r1.tags) == 0
-#    
-#    def test_14_revision_has_state(self):
-#        rev1 = Session.query(Revision).get(self.rev1_id)
-#        assert rev1.state == State.ACTIVE
-#
 #    def test_15_diff(self):
 #        p1 = Session.query(Package).filter_by(name=self.name1).one()
 #        pr2, pr1 = all_revisions(p1)
 #        assert diff1['title'] == u'- None\n+ XYZ', diff1
 
 
-class _Test_03_StatefulVersioned:
+class Test_03_StatefulVersioned:
     @classmethod
     def setup_class(self):
         repo.rebuild_db()
         logger.debug('====== start Changeset 2')
         rev2 = repo.new_revision()
         newp1 = Session.query(Package).filter_by(name=self.name1).one()
-        # either one works
-        newp1.tags = []
-        # newp1.tags_active.clear()
-        assert len(newp1.tags_active) == 0
+        del newp1.tags[0]
         Session.commit()
         self.rev2_id = rev2.id
         Session.remove()
         self.tagname1 = 'geo'
         t1 = Session.query(Tag).filter_by(name=self.tagname1).one()
         assert t1
-        newp1.tags.append(t1)
+        newp1.tags = [t1]
         repo.commit_and_remove()
 
     @classmethod
     def teardown_class(self):
         Session.remove()
 
-    def test_0_remove_and_readd_m2m(self):
+    def test_0_m2m_ok_at_end(self):
         p1 = Session.query(Package).filter_by(name=self.name1).one()
-        assert len(p1.package_tags) == 2, p1.package_tags
-        assert len(p1.tags_active) == 1, p1.tags_active
         assert len(p1.tags) == 1
+        assert p1.tags[0].id == 1
         Session.remove()
 
-    def test_1_underlying_is_right(self):
-        rev1 = Session.query(Changeset).get(self.rev1_id)
-        ptrevs = Session.query(PackageTagChangeset).filter_by(revision_id=rev1.id).all()
-        assert len(ptrevs) == 2
-        for pt in ptrevs:
-            assert pt.state == State.ACTIVE
-
-        rev2 = Session.query(Changeset).get(self.rev2_id)
-        ptrevs = Session.query(PackageTagChangeset).filter_by(revision_id=rev2.id).all()
-        assert len(ptrevs) == 2
-        for pt in ptrevs:
-            assert pt.state == State.DELETED
-    
-    # test should be higher up but need at least 3 revisions for problem to
-    # show up
-    def test_2_get_as_of(self):
+    def test_2_m2m_history(self):
         p1 = Session.query(Package).filter_by(name=self.name1).one()
         rev2 = Session.query(Changeset).get(self.rev2_id)
-        # should be 2 deleted and 1 as None
-        ptrevs = [ pt.get_as_of(rev2) for pt in p1.package_tags ]
-        print ptrevs
-        print Session.query(PackageTagChangeset).all()
-        assert ptrevs[0].changeset_id == rev2.id
+        changeobjects = all_revisions(p1)
+        for x in changeobjects:
+            print x.data
+        co = changeobjects[-1]
+        assert len(co.data['tags']) == 2
+        co = changeobjects[1]
+        assert len(co.data['tags']) == 1, co.data['tags']
+        assert co.data['tags'][0][1] == 2, co.data['tags']
 
-    def test_3_remove_and_readd_m2m_2(self):
-        num_package_tags = 2
-        rev1 = Session.query(Changeset).get(self.rev1_id)
-        p1 = Session.query(Package).filter_by(name=self.name1).one()
-        p1rev = p1.get_as_of(rev1)
-        # NB: relations on revision object proxy to continuity
-        # (though with get_as_of revision set)
-        assert len(p1rev.package_tags) == num_package_tags
-        assert len(p1rev.tags) == 2
-        Session.remove()
-
-        rev2 = Session.query(Changeset).get(self.rev2_id)
-        p1 = Session.query(Package).filter_by(name=self.name1).one()
-        p2rev = p1.get_as_of(rev2)
-        assert p2rev.__class__ == PackageChangeset
-        assert len(p2rev.package_tags) == num_package_tags
-        print rev2.id
-        print p2rev.tags_active
-        assert len(p2rev.tags) == 0
-
-
-class _Test_04_StatefulVersioned2:
-    '''Similar to previous but setting m2m list using existing objects'''
-
-    def setup(self):
-        Session.remove()
-        repo.rebuild_db()
-        logger.info('====== TestStatefulVersioned2: start')
-
-        # create a package with some tags
-        rev1 = repo.new_revision()
-        self.name1 = 'anna'
-        p1 = Package(name=self.name1)
-        t1 = Tag(name='geo')
-        p1.tags.append(t1)
-        Session.add_all([p1,t1])
-        Session.commit()
-        self.rev1_id = rev1.id
-        Session.remove()
-
-    def setup_method(self, name=''):
-        self.setup()
-        
-    @classmethod
-    def teardown_class(self):
-        Session.remove()
-
-    def _test_package_tags(self, check_all_pkg_tags=True):
-        p1 = Session.query(Package).filter_by(name=self.name1).one()
-        assert len(p1.package_tags) == 2, p1.package_tags
-        all_pkg_tags = Session.query(PackageTag).all()
-        if check_all_pkg_tags:
-            assert len(all_pkg_tags) == 2
-
-    def _test_tags(self):
-        p1 = Session.query(Package).filter_by(name=self.name1).one()
-        assert len(p1.tags) == 2, p1.tags
-
-    def test_1(self):
-        rev2 = repo.new_revision()
-        newp1 = Session.query(Package).filter_by(name=self.name1).one()
-        t1 = Session.query(Tag).filter_by(name='geo').one()
-        t2 = Tag(name='geo2')
-        newp1.tags = [ t1, t2 ]
-        repo.commit_and_remove()
-
-        self._test_package_tags()
-        self._test_tags()
-    
-    def test_2(self):
-        rev2 = repo.new_revision()
-        newp1 = Session.query(Package).filter_by(name=self.name1).one()
-        t1 = Session.query(Tag).filter_by(name='geo').one()
-        t2 = Tag(name='geo2')
-        print '**** setting tags'
-        newp1.tags[:] = [ t1, t2 ]
-        repo.commit_and_remove()
-
-        # TODO: (?) check on No of PackageTags fails
-        # the story is that an extra PackageTag for first tag gets constructed
-        # even though existing in deleted state (as expected)
-        # HOWEVER (unlike in 3 other cases in this class) this PackageTag is
-        # *already committed* when it arrives at _check_for_existing_on_add and
-        # therefore expunge has no effect on it (we'd need to delete and that
-        # may start getting 'hairy' ...)
-        self._test_package_tags(check_all_pkg_tags=False)
-        self._test_tags()
-
-    def test_3(self):
-        rev2 = repo.new_revision()
-        newp1 = Session.query(Package).filter_by(name=self.name1).one()
-        t1 = Session.query(Tag).filter_by(name='geo').one()
-        t2 = Tag(name='geo2')
-        newp1.tags[0] = t1
-        newp1.tags.append(t2)
-        repo.commit_and_remove()
-
-        self._test_package_tags()
-        self._test_tags()
-
-    def test_4(self):
-        rev2 = repo.new_revision()
-        newp1 = Session.query(Package).filter_by(name=self.name1).one()
-        t1 = Session.query(Tag).filter_by(name='geo').one()
-        t2 = Tag(name='geo2')
-        newp1.tags = [ t1, t2 ]
-        newp1.tags[0] = t1
-        del newp1.tags[1]
-        newp1.tags.append(t2)
-        # NB: doing this the other way round will result in 3 PackageTags
-        # newp1.tags.append(t2)
-        # del newp1.tags[1]
-        # this is because our system can't work out that we've just added and
-        # deleted the same tag
-        repo.commit_and_remove()
-
-        self._test_package_tags()
-        self._test_tags()
-
-
-class _Test_05_RevertAndPurge:
-
-    @classmethod
-    def setup_class(self):
-        Session.remove()
-        repo.rebuild_db()
-
-        rev1 = Changeset()
-        Session.add(rev1)
-        vdm.sqlalchemy.SQLAlchemySession.set_revision(Session, rev1)
-        
-        self.name1 = 'anna'
-        p1 = Package(name=self.name1)
-        p2 = Package(name='blahblah')
-        Session.add_all([p1,p2])
-        repo.commit_and_remove()
-
-        self.name2 = 'warandpeace'
-        self.lname = 'testlicense'
-        rev2 = repo.new_revision()
-        p1 = Session.query(Package).filter_by(name=self.name1).one()
-        p1.name = self.name2
-        l1 = License(name=self.lname)
-        Session.add_all([p1,l1])
-        repo.commit()
-        self.rev2id = rev2.id
-        Session.remove()
-
-    @classmethod
-    def teardown_class(self):
-        Session.remove()
-        repo.rebuild_db()
-
-    def test_basics(self):
-        revs = Session.query(Changeset).all()
-        assert len(revs) == 2
-        p1 = Session.query(Package).filter_by(name=self.name2).one()
-        assert p1.name == self.name2
-        assert len(Session.query(Package).all()) == 2
-
-    def test_list_changes(self):
-        rev2 = Session.query(Changeset).get(self.rev2id)
-        out = repo.list_changes(rev2)
-        assert len(out) == 3
-        assert len(out[Package]) == 1, out
-        assert len(out[License]) == 1, out
-
-    def test_purge_revision(self):
-        logger.debug('BEGINNING PURGE REVISION')
-        Session.remove()
-        rev2 = Session.query(Changeset).get(self.rev2id)
-        repo.purge_revision(rev2)
-        revs = Session.query(Changeset).all()
-        assert len(revs) == 1
-        p1 = Session.query(Package).filter_by(name=self.name1).first()
-        assert p1 is not None
-        assert len(Session.query(License).all()) == 0
-        pkgs = Session.query(Package).all()
-        assert len(pkgs) == 2, pkgrevs
-        pkgrevs = Session.query(PackageChangeset).all()
-        assert len(pkgrevs) == 2, pkgrevs
-
Tip: Filter by directory path e.g. /media app.js to search for public/media/app.js.
Tip: Use camelCasing e.g. ProjME to search for ProjectModifiedEvent.java.
Tip: Filter by extension type e.g. /repo .js to search for all .js files in the /repo directory.
Tip: Separate your search with spaces e.g. /ssh pom.xml to search for src/ssh/pom.xml.
Tip: Use ↑ and ↓ arrow keys to navigate and return to view the file.
Tip: You can also navigate files with Ctrl+j (next) and Ctrl+k (previous) and view the file with Ctrl+o.
Tip: You can also navigate files with Alt+j (next) and Alt+k (previous) and view the file with Alt+o.