Skip to content

Commit

Permalink
managed_transaction context manager can now adopt modified objects from
Browse files Browse the repository at this point in the history
  outer transaction
  • Loading branch information
vangheem committed May 18, 2017
1 parent 4367b4b commit cea172a
Show file tree
Hide file tree
Showing 4 changed files with 73 additions and 4 deletions.
4 changes: 3 additions & 1 deletion CHANGELOG.rst
@@ -1,7 +1,9 @@
1.0.0a28 (unreleased)
---------------------

- Nothing changed yet.
- managed_transaction context manager can now adopt modified objects from
outer transaction
[vangheem]


1.0.0a27 (2017-05-17)
Expand Down
6 changes: 4 additions & 2 deletions guillotina/catalog/index.py
Expand Up @@ -28,8 +28,10 @@ async def __call__(self, trns):
# Commits are run in sync thread so there is no asyncloop
search = queryUtility(ICatalogUtility)
if search:
await search.remove(self.container, self.remove)
await search.index(self.container, self.index)
if len(self.remove) > 0:
await search.remove(self.container, self.remove)
if len(self.index) > 0:
await search.index(self.container, self.index)

self.index = {}
self.remove = []
Expand Down
27 changes: 27 additions & 0 deletions guillotina/tests/test_transactions.py
@@ -1,5 +1,7 @@
from guillotina.db.transaction import Transaction
from guillotina.tests import mocks
from guillotina.tests import utils
from guillotina.transactions import managed_transaction


async def test_no_tid_created_for_reads(dummy_request, loop):
Expand All @@ -16,3 +18,28 @@ async def test_tid_created_for_writes(dummy_request, loop):
trns = Transaction(tm, dummy_request, loop=loop)
await trns.tpc_begin(None)
assert trns._tid is 1


async def test_managed_transaction_with_adoption(container_requester):
async with await container_requester as requester:
request = utils.get_mocked_request(requester.db)
root = await utils.get_root(request)
async with managed_transaction(request=request, abort_when_done=True):
container = await root.async_get('guillotina')
container.title = 'changed title'
container._p_register()

assert container._p_oid in container._p_jar.modified

# nest it with adoption
async with managed_transaction(request=request, adopt_parent_txn=True):
# this should commit, take on parent txn for container
pass

# no longer modified, adopted in previous txn
assert container._p_oid not in container._p_jar.modified

# finally, retrieve it again and make sure it's updated
async with managed_transaction(request=request, abort_when_done=True):
container = await root.async_get('guillotina')
assert container.title == 'changed title'
40 changes: 39 additions & 1 deletion guillotina/transactions.py
Expand Up @@ -61,14 +61,17 @@ def get_transaction(request=None):


class managed_transaction:
def __init__(self, request=None, tm=None, write=False, abort_when_done=False):
def __init__(self, request=None, tm=None, write=False, abort_when_done=False,
adopt_parent_txn=False):
self.request = _safe_get_request(request)
if tm is None:
tm = request._tm
self.tm = tm
self.write = write
self.abort_when_done = abort_when_done
self.previous_txn = self.txn = self.previous_write_setting = None
self.adopt_parent_txn = adopt_parent_txn
self.adopted = []

async def __aenter__(self):
if self.request is not None:
Expand All @@ -78,11 +81,46 @@ async def __aenter__(self):
self.request._db_write_enabled = True
self.txn = await self.tm.begin(request=self.request)

def adopt_objects(self, obs, txn):
for oid, ob in obs.items():
self.adopted.append(ob)
ob._p_jar = txn

async def __aexit__(self, exc_type, exc, tb):
if self.adopt_parent_txn:
# take on parent's modified, added, deleted objects if necessary
# before we commit or abort this transaction.
# this is necessary because inside this block, the outer transaction
# could have been attached to an object that changed.
# we're ready to commit and we want to potentially commit everything
# where, we we're adopted those objects with this transaction
if self.previous_txn != self.txn:
# try adopting currently registered objects
self.txn.modified = self.previous_txn.modified
self.txn.deleted = self.previous_txn.deleted
self.txn.added = self.previous_txn.added

self.adopt_objects(self.txn.modified, self.txn)
self.adopt_objects(self.txn.deleted, self.txn)
self.adopt_objects(self.txn.added, self.txn)

if self.abort_when_done:
await self.tm.abort(txn=self.txn)
else:
await self.tm.commit(txn=self.txn)

if self.adopt_parent_txn:
# restore transaction ownership of item from adoption done above
if self.previous_txn != self.txn:
# we adopted previously detetected transaction so now
# we need to clear changed objects and restore ownership
self.previous_txn.modified = {}
self.previous_txn.deleted = {}
self.previous_txn.added = {}

for ob in self.adopted:
ob._p_jar = self.previous_txn

if self.request is not None:
if self.previous_txn is not None:
# we do not want to overwrite _txn if is it None since we can
Expand Down

0 comments on commit cea172a

Please sign in to comment.