def new_revision( session: Session, tm: TransactionManager, content: Content, force_create_new_revision: bool = False, ) -> Content: """ Prepare context to update a Content. It will add a new updatable revision to the content. :param session: Database _session :param tm: TransactionManager :param content: Content instance to update :param force_create_new_revision: Decide if new_rev should or should not be forced. :return: """ with session.no_autoflush: try: if force_create_new_revision \ or inspect(content.revision).has_identity: content.new_revision() RevisionsIntegrity.add_to_updatable(content.revision) yield content except Exception as e: # INFO - GM - 14-11-2018 - rollback session and renew # transaction when error happened # This avoid bad _session data like new "temporary" revision # to be add when problem happen. session.rollback() tm.abort() tm.begin() raise e finally: RevisionsIntegrity.remove_from_updatable(content.revision)
def test_explicit_mode(self): from .. import TransactionManager from ..interfaces import AlreadyInTransaction, NoTransaction tm = TransactionManager() self.assertFalse(tm.explicit) tm = TransactionManager(explicit=True) self.assertTrue(tm.explicit) for name in 'get', 'commit', 'abort', 'doom', 'isDoomed', 'savepoint': with self.assertRaises(NoTransaction): getattr(tm, name)() t = tm.begin() with self.assertRaises(AlreadyInTransaction): tm.begin() self.assertTrue(t is tm.get()) self.assertFalse(tm.isDoomed()) tm.doom() self.assertTrue(tm.isDoomed()) tm.abort() for name in 'get', 'commit', 'abort', 'doom', 'isDoomed', 'savepoint': with self.assertRaises(NoTransaction): getattr(tm, name)() t = tm.begin() self.assertFalse(tm.isDoomed()) with self.assertRaises(AlreadyInTransaction): tm.begin() tm.savepoint() tm.commit()
def test_bigfile_filezodb_vs_conflicts(): root = dbopen() conn = root._p_jar db = conn.db() conn.close() del root, conn tm1 = TransactionManager() tm2 = TransactionManager() conn1 = db.open(transaction_manager=tm1) root1 = conn1.root() # setup zfile with fileh view to it root1['zfile3a'] = f1 = ZBigFile(blksize) tm1.commit() fh1 = f1.fileh_open() tm1.commit() # set zfile initial data vma1 = fh1.mmap(0, 1) Blk(vma1, 0)[0] = 1 tm1.commit() # read zfile and setup fileh for it in conn2 conn2 = db.open(transaction_manager=tm2) root2 = conn2.root() f2 = root2['zfile3a'] fh2 = f2.fileh_open() vma2 = fh2.mmap(0, 1) assert Blk(vma2, 0)[0] == 1 # read data in conn2 + make sure read correctly # now zfile content is both in ZODB.Connection cache and in _ZBigFileH # cache for each conn1 and conn2. Modify data in both conn1 and conn2 and # see how it goes. Blk(vma1, 0)[0] = 11 Blk(vma2, 0)[0] = 12 # txn1 should commit ok tm1.commit() # txn2 should raise ConflictError and stay at 11 state raises(ConflictError, 'tm2.commit()') tm2.abort() assert Blk(vma2, 0)[0] == 11 # re-read in conn2 Blk(vma2, 0)[0] = 13 tm2.commit() assert Blk(vma1, 0)[0] == 11 # not yet propagated to conn1 tm1.commit() # transaction boundary assert Blk(vma1, 0)[0] == 13 # re-read in conn1 conn2.close() dbclose(root1)
def test_zbigarray_vs_conflicts_metadata(): root = testdb.dbopen() conn = root._p_jar db = conn.db() conn.close() del root, conn tm1 = TransactionManager() tm2 = TransactionManager() conn1 = db.open(transaction_manager=tm1) root1 = conn1.root() # setup zarray root1['zarray3b'] = a1 = ZBigArray((10,), uint8) tm1.commit() # set zarray initial data a1[0:1] = [1] # XXX -> [0] = 1 after BigArray can tm1.commit() # read zarray in conn2 conn2 = db.open(transaction_manager=tm2) root2 = conn2.root() a2 = root2['zarray3b'] assert a2[0:1] == [1] # read data in conn2 + make sure read correctly # XXX -> [0] == 1 after BigArray can # now zarray content is both in ZODB.Connection cache and in _ZBigFileH # cache for each conn1 and conn2. Resize arrays in both conn1 and conn2 and # see how it goes. a1.resize((11,)) a2.resize((12,)) # txn1 should commit ok tm1.commit() # txn2 should raise ConflictError and stay at 11 state raises(ConflictError, 'tm2.commit()') tm2.abort() assert len(a2) == 11 # re-read in conn2 a2.resize((13,)) tm2.commit() assert len(a1) == 11 # not yet propagated to conn1 tm1.commit() # transaction boundary assert len(a1) == 13 # re-read in conn1 conn2.close() dbclose(root1)
def update_schema_manager(event): # Use a local transaction manager to sidestep any issues # with an active transaction or explicit mode. txm = TransactionManager(explicit=True) txm.begin() with contextlib.closing(event.database.open(txm)) as conn: state = conn.root().get(PersistentWebhookSchemaManager.key, State()) txm.abort() schema = get_schema_manager() schema.compareSubscriptionsAndComputeGeneration(state)
def evolve(self): """Perform a requested evolution This method needs to use the component architecture, so we'll set it up: >>> from zope.component.testing import setUp, tearDown >>> setUp() We also need a test request: >>> from zope.publisher.browser import TestRequest >>> request = TestRequest() We also need to give it a publication with a database: >>> class Publication(object): ... pass >>> request.setPublication(Publication()) >>> from ZODB.tests.util import DB >>> db = DB() >>> request.publication.db = db We need to define some schema managers. We'll define two using the demo package: >>> from zope.generations.generations import SchemaManager >>> from zope import component as ztapi >>> app1 = SchemaManager(0, 1, 'zope.generations.demo') >>> ztapi.provideUtility(app1, ISchemaManager, 'foo.app1') >>> app2 = SchemaManager(0, 0, 'zope.generations.demo') >>> ztapi.provideUtility(app2, ISchemaManager, 'foo.app2') And we need to record some data for them in the database. >>> from zope.generations.generations import evolve >>> evolve(db) This sets up the data and actually evolves app1: >>> conn = db.open() >>> conn.root()[generations_key]['foo.app1'] 1 >>> conn.root()[generations_key]['foo.app2'] 0 To evolve a data base schema, the user clicks on a submit button. If they click on the button for add1, a item will be added to the request, which we simulate: >>> request.form['evolve-app-foo.app1'] = 'evolve' We'll also increase the generation of app1: >>> app1.generation = 2 Now we can create our view: >>> view = Managers(None, request) Now, if we call its `evolve` method, it should see that the app1 evolve button was pressed and evolve app1 to the next generation. >>> status = view.evolve() >>> conn.sync() >>> conn.root()[generations_key]['foo.app1'] 2 The demo evolver just writes the generation to a database key: >>> from zope.generations.demo import key >>> conn.root()[key] ('installed', 'installed', 2) Note that, because the demo package has an install script, we have entries for that script. Which the returned status should indicate: >>> status['app'] u'foo.app1' >>> status['to'] 2 Now, given that the database is at the maximum generation for app1, we can't evolve it further. Calling evolve again won't evolve anything: >>> status = view.evolve() >>> conn.sync() >>> conn.root()[generations_key]['foo.app1'] 2 >>> conn.root()[key] ('installed', 'installed', 2) as the status will indicate by returning a 'to' generation of 0: >>> status['app'] u'foo.app1' >>> status['to'] 0 If the request doesn't have the key: >>> request.form.clear() Then calling evolve does nothing: >>> view.evolve() >>> conn.sync() >>> conn.root()[generations_key]['foo.app1'] 2 >>> conn.root()[key] ('installed', 'installed', 2) We'd better clean upp: >>> db.close() >>> tearDown() """ self.managers = managers = dict( zope.component.getUtilitiesFor(ISchemaManager)) db = self._getdb() transaction_manager = TransactionManager() conn = db.open(transaction_manager=transaction_manager) transaction_manager.begin() try: generations = conn.root().get(generations_key, ()) request = self.request for key in generations: generation = generations[key] rkey = request_key_format % key if rkey in request: manager = managers[key] if generation >= manager.generation: return {'app': key, 'to': 0} context = Context() context.connection = conn generation += 1 manager.evolve(context, generation) generations[key] = generation transaction_manager.commit() return {'app': key, 'to': generation} finally: transaction_manager.abort() conn.close()
def applications(self): """Get information about database-generation status This method needs to use the component architecture, so we'll set it up: >>> from zope.component.testing import setUp, tearDown >>> setUp() We also need a test request: >>> from zope.publisher.browser import TestRequest >>> request = TestRequest() We also need to give it a publication with a database: >>> class Publication(object): ... pass >>> request.setPublication(Publication()) >>> from ZODB.tests.util import DB >>> db = DB() >>> request.publication.db = db We need to define some schema managers. We'll define two using the demo package: >>> from zope.generations.generations import SchemaManager >>> from zope import component as ztapi >>> app1 = SchemaManager(0, 1, 'zope.generations.demo') >>> ztapi.provideUtility(app1, ISchemaManager, 'foo.app1') >>> app2 = SchemaManager(0, 0, 'zope.generations.demo') >>> ztapi.provideUtility(app2, ISchemaManager, 'foo.app2') And we need to record some data for them in the database. >>> from zope.generations.generations import evolve >>> evolve(db) This sets up the data and actually evolves app1: >>> conn = db.open() >>> conn.root()[generations_key]['foo.app1'] 1 >>> conn.root()[generations_key]['foo.app2'] 0 Now, let's increment app1's generation: >>> app1.generation += 1 so we can evolve it. Now we can create our view: >>> view = Managers(None, request) We call its applications method to get data about application generations. We are required to call evolve first: >>> view.evolve() >>> data = list(view.applications()) >>> data.sort(key=lambda d1: d1['id']) >>> for info in data: ... print(info['id']) ... print(info['min'], info['max'], info['generation']) ... print('evolve?', info['evolve'] or None) foo.app1 0 2 1 evolve? evolve-app-foo.app1 foo.app2 0 0 0 evolve? None We'd better clean up: >>> db.close() >>> tearDown() """ result = [] db = self._getdb() transaction_manager = TransactionManager() conn = db.open(transaction_manager=transaction_manager) transaction_manager.begin() try: managers = self.managers generations = conn.root().get(generations_key, ()) for key, generation in generations.items(): manager = managers.get(key) if manager is None: # pragma: no cover continue result.append({ 'id': key, 'min': manager.minimum_generation, 'max': manager.generation, 'generation': generation, 'evolve': (generation < manager.generation and request_key_format % key or '' ), }) return result finally: transaction_manager.abort() conn.close()