From ae43b6f2a9a46e88dcc853542fd5a6323cae7ccb Mon Sep 17 00:00:00 2001 From: Daniel Dotsenko Date: Wed, 9 Apr 2014 13:27:08 -0700 Subject: [PATCH 1/4] adding callbacks support to BatchQuery --- cqlengine/query.py | 45 ++++++++++- cqlengine/tests/test_batch_query.py | 113 +++++++++++++++++++++++++--- docs/topics/queryset.rst | 93 +++++++++++++++++++++++ 3 files changed, 239 insertions(+), 12 deletions(-) diff --git a/cqlengine/query.py b/cqlengine/query.py index eb82479b..8586ca57 100644 --- a/cqlengine/query.py +++ b/cqlengine/query.py @@ -78,8 +78,35 @@ class BatchQuery(object): """ _consistency = None + @property + def callbacks(self): + """ + Returns the set object that contains all of the callbacks presently attached to this batch. - def __init__(self, batch_type=None, timestamp=None, consistency=None, execute_on_exception=False): + :rtype: set + """ + return self._callbacks + + def __init__(self, batch_type=None, timestamp=None, consistency=None, execute_on_exception=False, callbacks=None): + """ + :param batch_type: (optional) One of batch type values available through BatchType enum + :type batch_type: str or None + :param timestamp: (optional) A datetime or timedelta object with desired timestamp to be applied + to the batch transaction. + :type timestamp: datetime or timedelta or None + :param consistency: (optional) One of consistency values ("ANY", "ONE", "QUORUM" etc) + :type consistency: str or None + :param execute_on_exception: (Defaults to False) Indicates that when the BatchQuery instance is used + as a context manager the queries accumulated within the context must be executed despite + encountering an error within the context. By default, any exception raised from within + the context scope will cause the batched queries not to be executed. + :type execute_on_exception: bool + :param callbacks: A list of functions to be executed after the batch executes. Note, that if the batch + does not execute, the callbacks are not executed. This, thus, effectively is a list of "on success" + callback handlers. If defined, must be a collection of callables. + At this time only one argument is passed into the callback - the batch instance. + :type callbacks: list or set or tuple + """ self.queries = [] self.batch_type = batch_type if timestamp is not None and not isinstance(timestamp, (datetime, timedelta)): @@ -87,6 +114,7 @@ class BatchQuery(object): self.timestamp = timestamp self._consistency = consistency self._execute_on_exception = execute_on_exception + self._callbacks = set(callbacks or []) def add_query(self, query): if not isinstance(query, BaseCQLStatement): @@ -96,9 +124,23 @@ class BatchQuery(object): def consistency(self, consistency): self._consistency = consistency + def _execute_callbacks(self): + for callback in self._callbacks: + if callable(callback): + stored_args = [] + else: + stored_args = callback[1:] + callback = callback[0] + callback(self, *stored_args) + + # trying to clear up the ref counts for objects mentioned in the set + del self._callbacks + def execute(self): if len(self.queries) == 0: # Empty batch is a no-op + # except for callbacks + self._execute_callbacks() return opener = 'BEGIN ' + (self.batch_type + ' ' if self.batch_type else '') + ' BATCH' @@ -130,6 +172,7 @@ class BatchQuery(object): execute('\n'.join(query_list), parameters, self._consistency) self.queries = [] + self._execute_callbacks() def __enter__(self): return self diff --git a/cqlengine/tests/test_batch_query.py b/cqlengine/tests/test_batch_query.py index 4beb33d9..da7c944f 100644 --- a/cqlengine/tests/test_batch_query.py +++ b/cqlengine/tests/test_batch_query.py @@ -17,6 +17,7 @@ class TestMultiKeyModel(Model): count = columns.Integer(required=False) text = columns.Text(required=False) + class BatchQueryTests(BaseCassEngTestCase): @classmethod @@ -41,24 +42,13 @@ class BatchQueryTests(BaseCassEngTestCase): b = BatchQuery() inst = TestMultiKeyModel.batch(b).create(partition=self.pkey, cluster=2, count=3, text='4') - with self.assertRaises(TestMultiKeyModel.DoesNotExist): TestMultiKeyModel.get(partition=self.pkey, cluster=2) b.execute() - TestMultiKeyModel.get(partition=self.pkey, cluster=2) - def test_batch_is_executed(self): - b = BatchQuery() - inst = TestMultiKeyModel.batch(b).create(partition=self.pkey, cluster=2, count=3, text='4') - - with self.assertRaises(TestMultiKeyModel.DoesNotExist): - TestMultiKeyModel.get(partition=self.pkey, cluster=2) - - b.execute() - def test_update_success_case(self): inst = TestMultiKeyModel.create(partition=self.pkey, cluster=2, count=3, text='4') @@ -125,3 +115,104 @@ class BatchQueryTests(BaseCassEngTestCase): with BatchQuery() as b: pass + +class BatchQueryCallbacksTests(BaseCassEngTestCase): + + def test_API_managing_callbacks(self): + + # Callbacks can be added at init and after + + def my_callback(batch, *args, **kwargs): + pass + + # adding on init: + b = BatchQuery(callbacks=[ + my_callback, + my_callback, # will be filtered out automatically + (my_callback, 'more', 'args'), + (my_callback, 'more', 'args'), # will be filtered out too + ]) + assert b.callbacks == { + my_callback, + (my_callback, 'more', 'args') + } + + # adding using set API post-init: + + b.callbacks.add(my_callback) + b.callbacks.add((my_callback, 'more', 'args')) + b.callbacks.add((my_callback, 'yet more', 'args')) + assert b.callbacks == { + my_callback, + (my_callback, 'more', 'args'), + (my_callback, 'yet more', 'args') + } + + b.callbacks.remove(my_callback) + assert b.callbacks == { + (my_callback, 'more', 'args'), + (my_callback, 'yet more', 'args') + } + + b.callbacks.remove((my_callback, 'more', 'args')) + assert b.callbacks == { + (my_callback, 'yet more', 'args') + } + + + # insure that we disallow setting the callback colleciton on instance: + # (thus forcing use of init and set-like api tested above) + with self.assertRaises(AttributeError): + b.callbacks = set() + + def test_callbacks_properly_execute_callables_and_tuples(self): + + call_history = [] + def my_callback(*args, **kwargs): + call_history.append(args) + + # adding on init: + b = BatchQuery(callbacks=[ + my_callback, + (my_callback, 'more', 'args') + ]) + + b.execute() + + assert len(call_history) == 2 + assert {(b,), (b, 'more', 'args')} == set(call_history) + + def test_callbacks_tied_to_execute(self): + """Batch callbacks should NOT fire if batch is not executed in context manager mode""" + + call_history = [] + def my_callback(*args, **kwargs): + call_history.append(args) + + with BatchQuery(callbacks=[my_callback]) as b: + pass + + assert len(call_history) == 1 + + class SomeError(Exception): + pass + + with self.assertRaises(SomeError): + with BatchQuery(callbacks=[my_callback]) as b: + # this error bubbling up through context manager + # should prevent callback runs (along with b.execute()) + raise SomeError + + # still same call history + assert len(call_history) == 1 + + # but if execute run, even with an error bubbling through + # the callbacks also whoudl have fired + with self.assertRaises(SomeError): + with BatchQuery(execute_on_exception=True, callbacks=[my_callback]) as b: + # this error bubbling up through context manager + # should prevent callback runs (along with b.execute()) + raise SomeError + + # still same call history + assert len(call_history) == 2 diff --git a/docs/topics/queryset.rst b/docs/topics/queryset.rst index 33236040..58634446 100644 --- a/docs/topics/queryset.rst +++ b/docs/topics/queryset.rst @@ -274,6 +274,9 @@ Batch Queries cqlengine now supports batch queries using the BatchQuery class. Batch queries can be started and stopped manually, or within a context manager. To add queries to the batch object, you just need to precede the create/save/delete call with a call to batch, and pass in the batch object. +Batch Query General Use Pattern +------------------------------- + You can only create, update, and delete rows with a batch query, attempting to read rows out of the database with a batch query will fail. .. code-block:: python @@ -324,6 +327,96 @@ Batch Queries If an exception is thrown somewhere in the block, any statements that have been added to the batch will still be executed. This is useful for some logging situations. +Batch Query Execution Callbacks +------------------------------- + + In order to allow secondary tasks to be chained to the end of batch, BatchQuery instances allow callbacks to be + registered with the batch, to be executed immediately after the batch executes. + + Let's say an iterative action over a group of records needs to be followed up by a "maintenance" task like "Update + count of children on parent model." + If your were to kick off that task following manipulation on each child, while the batch is not executed, the reads + the maintenance task would do would operate on old data since the batch is not yet commited. + (You also risk kicking off too many of the same tasks, while only one is needed at the end of the batch.) + + The callbacks attached to a given batch instance are executed only if the batch executes. If the batch is used as a + context manager and an exception bubbles up, the queued up callbacks will not be run. + + The callback should be a callable object that accepts at least one (positional) argument - instance of the batch object. + + .. code-block:: python + + def example_callback_handler(batch, *args, **kwargs): + pass + + Multiple callbacks can be attached to same BatchQuery instance both, at the point of instantiation and later + directly to the batch object instance. + + The callbacks collection is implemented as a ``set``, which naturally deduplicates the callback handlers: + + .. code-block:: python + + # let's add a few of the same callbacks at init + batch = BatchQuery(callbacks=([ + example_callback_handler, + example_callback_handler + ]) + + # and one more time same callback now using the collection API on the batch instance + batch.callbacks.add(example_callback_handler) + + assert len(batch.callbacks) == 1 + assert batch.callbacks == {example_callback_handler} + + # this, of course, also means that if you remove a callback, + # you negate "all additions" of that callback + batch.callbacks.remove(example_callback_handler) + + assert len(batch.callbacks) == 0 + + In order to benefit from the natural deduplication of the callback handlers it is allowed to pass in tuples of + callback and its positional arguments. + This allows one to pre-package additional call arguments for the callable without the need to wrap the callable and + args into a closure or ``functools.partial``. + + .. code-block:: python + + batch = BatchQuery(callbacks=([ + (example_callback_handler, 'some', 'args'), + (example_callback_handler, 'some', 'args'), + (example_callback_handler, 'some other', 'args'), + ]) + + batch.callbacks.add((example_callback_handler, 'some other', 'args')) + + assert len(batch.callbacks) == 2 + assert batch.callbacks == { + (example_callback_handler, 'some', 'args'), + (example_callback_handler, 'some other', 'args') + } + + Note that the tuple callback object format prevents the possibily of essentially same callback firing multiple times, + which might happen if you package same callback and arguments several times with ``functools.partial``, which returns a new callable with each call. + + Arguments packaged into callbacks communicated as tuples are passed to callback handler **after** the ``batch`` argument. + + .. code-block:: python + + call_args = [] + + def callback_handler(batch, *args, **kwargs): + call_args.append([batch] + list(args)) + + batch = BatchQuery(callbacks=([(callback_handler, 'some', 'args')]) + + batch.execute() + + assert call_args == [[batch, 'some', 'args']] + + + Failure in any of the callbacks does not affect the batch's execution, as the callbacks are started after the execution + of the batch is complete and no effort to "roll it back" is made. + QuerySet method reference From 86ffd6a9741872c3094da22bac63b082e74ffe82 Mon Sep 17 00:00:00 2001 From: Daniel Dotsenko Date: Mon, 14 Apr 2014 11:56:30 -0700 Subject: [PATCH 2/4] removing `batch` from the list of args passed to BatchQuery callbacks --- cqlengine/query.py | 3 +-- cqlengine/tests/test_batch_query.py | 2 +- docs/topics/queryset.rst | 19 ++++++++++--------- 3 files changed, 12 insertions(+), 12 deletions(-) diff --git a/cqlengine/query.py b/cqlengine/query.py index 8586ca57..f2b6da14 100644 --- a/cqlengine/query.py +++ b/cqlengine/query.py @@ -104,7 +104,6 @@ class BatchQuery(object): :param callbacks: A list of functions to be executed after the batch executes. Note, that if the batch does not execute, the callbacks are not executed. This, thus, effectively is a list of "on success" callback handlers. If defined, must be a collection of callables. - At this time only one argument is passed into the callback - the batch instance. :type callbacks: list or set or tuple """ self.queries = [] @@ -131,7 +130,7 @@ class BatchQuery(object): else: stored_args = callback[1:] callback = callback[0] - callback(self, *stored_args) + callback(*stored_args) # trying to clear up the ref counts for objects mentioned in the set del self._callbacks diff --git a/cqlengine/tests/test_batch_query.py b/cqlengine/tests/test_batch_query.py index da7c944f..f956323a 100644 --- a/cqlengine/tests/test_batch_query.py +++ b/cqlengine/tests/test_batch_query.py @@ -180,7 +180,7 @@ class BatchQueryCallbacksTests(BaseCassEngTestCase): b.execute() assert len(call_history) == 2 - assert {(b,), (b, 'more', 'args')} == set(call_history) + assert {tuple(), ('more', 'args')} == set(call_history) def test_callbacks_tied_to_execute(self): """Batch callbacks should NOT fire if batch is not executed in context manager mode""" diff --git a/docs/topics/queryset.rst b/docs/topics/queryset.rst index 58634446..c0566552 100644 --- a/docs/topics/queryset.rst +++ b/docs/topics/queryset.rst @@ -342,14 +342,16 @@ Batch Query Execution Callbacks The callbacks attached to a given batch instance are executed only if the batch executes. If the batch is used as a context manager and an exception bubbles up, the queued up callbacks will not be run. - The callback should be a callable object that accepts at least one (positional) argument - instance of the batch object. + The callback arguments signature is not prescribed. However, while it's possible to trap arguments you want to be + passed into the callback in a "callback" tuple you add to the batch, only passing of positional arguments is + supported at this time. This means there is no elegant way to communicate named arguments to the callback. .. code-block:: python - def example_callback_handler(batch, *args, **kwargs): + def example_callback_handler(*args): pass - Multiple callbacks can be attached to same BatchQuery instance both, at the point of instantiation and later + Multiple callbacks can be attached to same BatchQuery instance both, at the point of batch instantiation and later, directly to the batch object instance. The callbacks collection is implemented as a ``set``, which naturally deduplicates the callback handlers: @@ -396,22 +398,21 @@ Batch Query Execution Callbacks } Note that the tuple callback object format prevents the possibily of essentially same callback firing multiple times, - which might happen if you package same callback and arguments several times with ``functools.partial``, which returns a new callable with each call. - - Arguments packaged into callbacks communicated as tuples are passed to callback handler **after** the ``batch`` argument. + which might happen if you package same callback and arguments several times with ``functools.partial``, which returns + a new callable with each call. .. code-block:: python call_args = [] - def callback_handler(batch, *args, **kwargs): - call_args.append([batch] + list(args)) + def callback_handler(*args, **kwargs): + call_args.append(list(args)) batch = BatchQuery(callbacks=([(callback_handler, 'some', 'args')]) batch.execute() - assert call_args == [[batch, 'some', 'args']] + assert call_args[0] == ['some', 'args'] Failure in any of the callbacks does not affect the batch's execution, as the callbacks are started after the execution From 8093ac4bad555bf2f2f06c46b120a0cd7752a945 Mon Sep 17 00:00:00 2001 From: Daniel Dotsenko Date: Mon, 14 Apr 2014 18:10:48 -0700 Subject: [PATCH 3/4] switching to list as BatchQuery callbacks collection + add_callback() API --- cqlengine/query.py | 39 +++++++------- cqlengine/tests/test_batch_query.py | 74 +++++++++----------------- docs/topics/queryset.rst | 82 +++++++---------------------- 3 files changed, 66 insertions(+), 129 deletions(-) diff --git a/cqlengine/query.py b/cqlengine/query.py index f2b6da14..9e81f5a5 100644 --- a/cqlengine/query.py +++ b/cqlengine/query.py @@ -78,16 +78,7 @@ class BatchQuery(object): """ _consistency = None - @property - def callbacks(self): - """ - Returns the set object that contains all of the callbacks presently attached to this batch. - - :rtype: set - """ - return self._callbacks - - def __init__(self, batch_type=None, timestamp=None, consistency=None, execute_on_exception=False, callbacks=None): + def __init__(self, batch_type=None, timestamp=None, consistency=None, execute_on_exception=False): """ :param batch_type: (optional) One of batch type values available through BatchType enum :type batch_type: str or None @@ -113,7 +104,7 @@ class BatchQuery(object): self.timestamp = timestamp self._consistency = consistency self._execute_on_exception = execute_on_exception - self._callbacks = set(callbacks or []) + self._callbacks = [] def add_query(self, query): if not isinstance(query, BaseCQLStatement): @@ -124,17 +115,29 @@ class BatchQuery(object): self._consistency = consistency def _execute_callbacks(self): - for callback in self._callbacks: - if callable(callback): - stored_args = [] - else: - stored_args = callback[1:] - callback = callback[0] - callback(*stored_args) + for callback, args, kwargs in self._callbacks: + callback(*args, **kwargs) # trying to clear up the ref counts for objects mentioned in the set del self._callbacks + def add_callback(self, fn, *args, **kwargs): + """Add a function and arguments to be passed to it to be executed after the batch executes. + + A batch can support multiple callbacks. + + Note, that if the batch does not execute, the callbacks are not executed. + A callback, thus, is an "on batch success" handler. + + :param fn: Callable object + :type fn: callable + :param *args: Positional arguments to be passed to the callback at the time of execution + :param **kwargs: Named arguments to be passed to the callback at the time of execution + """ + if not callable(fn): + raise ValueError("Value for argument 'fn' is {} and is not a callable object.".format(type(fn))) + self._callbacks.append((fn, args, kwargs)) + def execute(self): if len(self.queries) == 0: # Empty batch is a no-op diff --git a/cqlengine/tests/test_batch_query.py b/cqlengine/tests/test_batch_query.py index f956323a..7066643d 100644 --- a/cqlengine/tests/test_batch_query.py +++ b/cqlengine/tests/test_batch_query.py @@ -122,48 +122,21 @@ class BatchQueryCallbacksTests(BaseCassEngTestCase): # Callbacks can be added at init and after - def my_callback(batch, *args, **kwargs): + def my_callback(*args, **kwargs): pass # adding on init: - b = BatchQuery(callbacks=[ - my_callback, - my_callback, # will be filtered out automatically - (my_callback, 'more', 'args'), - (my_callback, 'more', 'args'), # will be filtered out too - ]) - assert b.callbacks == { - my_callback, - (my_callback, 'more', 'args') - } + batch = BatchQuery() - # adding using set API post-init: + batch.add_callback(my_callback) + batch.add_callback(my_callback, 2, named_arg='value') + batch.add_callback(my_callback, 1, 3) - b.callbacks.add(my_callback) - b.callbacks.add((my_callback, 'more', 'args')) - b.callbacks.add((my_callback, 'yet more', 'args')) - assert b.callbacks == { - my_callback, - (my_callback, 'more', 'args'), - (my_callback, 'yet more', 'args') - } - - b.callbacks.remove(my_callback) - assert b.callbacks == { - (my_callback, 'more', 'args'), - (my_callback, 'yet more', 'args') - } - - b.callbacks.remove((my_callback, 'more', 'args')) - assert b.callbacks == { - (my_callback, 'yet more', 'args') - } - - - # insure that we disallow setting the callback colleciton on instance: - # (thus forcing use of init and set-like api tested above) - with self.assertRaises(AttributeError): - b.callbacks = set() + assert batch._callbacks == [ + (my_callback, (), {}), + (my_callback, (2,), {'named_arg':'value'}), + (my_callback, (1, 3), {}) + ] def test_callbacks_properly_execute_callables_and_tuples(self): @@ -172,15 +145,15 @@ class BatchQueryCallbacksTests(BaseCassEngTestCase): call_history.append(args) # adding on init: - b = BatchQuery(callbacks=[ - my_callback, - (my_callback, 'more', 'args') - ]) + batch = BatchQuery() - b.execute() + batch.add_callback(my_callback) + batch.add_callback(my_callback, 'more', 'args') + + batch.execute() assert len(call_history) == 2 - assert {tuple(), ('more', 'args')} == set(call_history) + assert [(), ('more', 'args')] == call_history def test_callbacks_tied_to_execute(self): """Batch callbacks should NOT fire if batch is not executed in context manager mode""" @@ -189,7 +162,8 @@ class BatchQueryCallbacksTests(BaseCassEngTestCase): def my_callback(*args, **kwargs): call_history.append(args) - with BatchQuery(callbacks=[my_callback]) as b: + with BatchQuery() as batch: + batch.add_callback(my_callback) pass assert len(call_history) == 1 @@ -198,18 +172,20 @@ class BatchQueryCallbacksTests(BaseCassEngTestCase): pass with self.assertRaises(SomeError): - with BatchQuery(callbacks=[my_callback]) as b: + with BatchQuery() as batch: + batch.add_callback(my_callback) # this error bubbling up through context manager # should prevent callback runs (along with b.execute()) raise SomeError - # still same call history + # still same call history. Nothing added assert len(call_history) == 1 - # but if execute run, even with an error bubbling through - # the callbacks also whoudl have fired + # but if execute ran, even with an error bubbling through + # the callbacks also would have fired with self.assertRaises(SomeError): - with BatchQuery(execute_on_exception=True, callbacks=[my_callback]) as b: + with BatchQuery(execute_on_exception=True) as batch: + batch.add_callback(my_callback) # this error bubbling up through context manager # should prevent callback runs (along with b.execute()) raise SomeError diff --git a/docs/topics/queryset.rst b/docs/topics/queryset.rst index c0566552..d751152f 100644 --- a/docs/topics/queryset.rst +++ b/docs/topics/queryset.rst @@ -339,81 +339,39 @@ Batch Query Execution Callbacks the maintenance task would do would operate on old data since the batch is not yet commited. (You also risk kicking off too many of the same tasks, while only one is needed at the end of the batch.) + Multiple callbacks can be attached to same BatchQuery instance + The callbacks attached to a given batch instance are executed only if the batch executes. If the batch is used as a context manager and an exception bubbles up, the queued up callbacks will not be run. - The callback arguments signature is not prescribed. However, while it's possible to trap arguments you want to be - passed into the callback in a "callback" tuple you add to the batch, only passing of positional arguments is - supported at this time. This means there is no elegant way to communicate named arguments to the callback. + The callback arguments signature is not prescribed. Moreover, the callback API allows trapping the arguments you want to be + passed into the callback. If you need to inspect the batch itself within the callback, simply trap the batch instance + as part of the arguments stored with the callback. + + Internally the callbacks collection is implemented as an ordered collection, which means the execution order follows + the order in which the callbacks are added to the batch. .. code-block:: python - def example_callback_handler(*args): + def my_callback(*args, **kwargs): pass - Multiple callbacks can be attached to same BatchQuery instance both, at the point of batch instantiation and later, - directly to the batch object instance. + batch = BatchQuery() - The callbacks collection is implemented as a ``set``, which naturally deduplicates the callback handlers: + batch.add_callback(my_callback) + batch.add_callback(my_callback, 'positional arg', named_arg='named arg value') - .. code-block:: python - - # let's add a few of the same callbacks at init - batch = BatchQuery(callbacks=([ - example_callback_handler, - example_callback_handler - ]) - - # and one more time same callback now using the collection API on the batch instance - batch.callbacks.add(example_callback_handler) - - assert len(batch.callbacks) == 1 - assert batch.callbacks == {example_callback_handler} - - # this, of course, also means that if you remove a callback, - # you negate "all additions" of that callback - batch.callbacks.remove(example_callback_handler) - - assert len(batch.callbacks) == 0 - - In order to benefit from the natural deduplication of the callback handlers it is allowed to pass in tuples of - callback and its positional arguments. - This allows one to pre-package additional call arguments for the callable without the need to wrap the callable and - args into a closure or ``functools.partial``. - - .. code-block:: python - - batch = BatchQuery(callbacks=([ - (example_callback_handler, 'some', 'args'), - (example_callback_handler, 'some', 'args'), - (example_callback_handler, 'some other', 'args'), - ]) - - batch.callbacks.add((example_callback_handler, 'some other', 'args')) - - assert len(batch.callbacks) == 2 - assert batch.callbacks == { - (example_callback_handler, 'some', 'args'), - (example_callback_handler, 'some other', 'args') - } - - Note that the tuple callback object format prevents the possibily of essentially same callback firing multiple times, - which might happen if you package same callback and arguments several times with ``functools.partial``, which returns - a new callable with each call. - - .. code-block:: python - - call_args = [] - - def callback_handler(*args, **kwargs): - call_args.append(list(args)) - - batch = BatchQuery(callbacks=([(callback_handler, 'some', 'args')]) + # if you need reference to the batch within the callback, + # just trap it in the arguments to be passed to the callback: + batch.add_callback(my_callback, cqlengine_batch=batch) + # once the batch executes... batch.execute() - assert call_args[0] == ['some', 'args'] - + # the effect of the above scheduled callbacks will be similar to + my_callback() + my_callback('positional arg', named_arg='named arg value') + my_callback(cqlengine_batch=batch) Failure in any of the callbacks does not affect the batch's execution, as the callbacks are started after the execution of the batch is complete and no effort to "roll it back" is made. From 70e58416d0753a081f35e35f6dfacd841afc222d Mon Sep 17 00:00:00 2001 From: Blake Eggleston Date: Thu, 17 Apr 2014 15:27:46 -0700 Subject: [PATCH 4/4] cleaning up batch callback docs --- docs/topics/queryset.rst | 20 ++++---------------- 1 file changed, 4 insertions(+), 16 deletions(-) diff --git a/docs/topics/queryset.rst b/docs/topics/queryset.rst index d751152f..a8eb2af5 100644 --- a/docs/topics/queryset.rst +++ b/docs/topics/queryset.rst @@ -333,23 +333,11 @@ Batch Query Execution Callbacks In order to allow secondary tasks to be chained to the end of batch, BatchQuery instances allow callbacks to be registered with the batch, to be executed immediately after the batch executes. - Let's say an iterative action over a group of records needs to be followed up by a "maintenance" task like "Update - count of children on parent model." - If your were to kick off that task following manipulation on each child, while the batch is not executed, the reads - the maintenance task would do would operate on old data since the batch is not yet commited. - (You also risk kicking off too many of the same tasks, while only one is needed at the end of the batch.) - - Multiple callbacks can be attached to same BatchQuery instance + Multiple callbacks can be attached to same BatchQuery instance, they are executed in the same order that they + are added to the batch. The callbacks attached to a given batch instance are executed only if the batch executes. If the batch is used as a - context manager and an exception bubbles up, the queued up callbacks will not be run. - - The callback arguments signature is not prescribed. Moreover, the callback API allows trapping the arguments you want to be - passed into the callback. If you need to inspect the batch itself within the callback, simply trap the batch instance - as part of the arguments stored with the callback. - - Internally the callbacks collection is implemented as an ordered collection, which means the execution order follows - the order in which the callbacks are added to the batch. + context manager and an exception is raised, the queued up callbacks will not be run. .. code-block:: python @@ -374,7 +362,7 @@ Batch Query Execution Callbacks my_callback(cqlengine_batch=batch) Failure in any of the callbacks does not affect the batch's execution, as the callbacks are started after the execution - of the batch is complete and no effort to "roll it back" is made. + of the batch is complete.