[Kimchi-devel] [PATCH] RollbackContext: Make it complied with Python Standard
Shu Ming
shuming at linux.vnet.ibm.com
Mon Jan 20 03:37:40 UTC 2014
于 2014/1/16 13:17, Zhou Zheng Sheng 写道:
> According to Python documentation, context managers should not re-raise
> the exception passed to __exit__ method. If __exit__() does not return
> True, Python re-raises this exception automatically. Context managers
> should only raise exception from __exit__() itself. This means we should
> only re-raise exception caught from the undo functions.
>
> Another problem of the current implementation is that it suppresses str
> exceptions. This is because of bug in old Python version. When an
> exception is raised from within a "with" statement, exc_value would be a
> raw str or int rather than Exception instance, so when we re-raise the
> stored exc_value, it triggers another exception saying "exceptions must
> be old-style classes or derived from BaseException, not str".
> This patch fix this problem by raising the stored exception value along
> with its store exception type.
>
> http://docs.python.org/2/library/stdtypes.html#contextmanager.__exit__
>
> Signed-off-by: Zhou Zheng Sheng <zhshzhou at linux.vnet.ibm.com>
> ---
> src/kimchi/rollbackcontext.py | 25 +++++------
> tests/test_rollbackcontext.py | 102 ++++++++++++++++++++++++++++++++++++++++++
> 2 files changed, 114 insertions(+), 13 deletions(-)
> create mode 100644 tests/test_rollbackcontext.py
>
> diff --git a/src/kimchi/rollbackcontext.py b/src/kimchi/rollbackcontext.py
> index 2afd114..9502169 100644
> --- a/src/kimchi/rollbackcontext.py
> +++ b/src/kimchi/rollbackcontext.py
> @@ -45,24 +45,23 @@ class RollbackContext(object):
> return self
>
> def __exit__(self, exc_type, exc_value, traceback):
> - firstException = exc_value
> -
> + """
> + According to Python official doc. This function should only re-raise
> + the exception from undo functions. Python automatically re-raises the
> + original exception when this function does not return True.
> + http://docs.python.org/2/library/stdtypes.html#contextmanager.__exit__
> + """
> + undoExcInfo = None
> for undo, args, kwargs in self._finally:
> try:
> undo(*args, **kwargs)
> - except Exception as e:
> + except Exception:
> # keep the earliest exception info
> - if not firstException:
> - firstException = e
> - # keep the original traceback info
> - traceback = sys.exc_info()[2]
> + if undoExcInfo is None:
I think undoExcInfo is always None here. Why should we check it?
> + undoExcInfo = sys.exc_info()
>
> - # re-raise the earliest exception
> - if firstException is not None:
> - if type(firstException) is str:
> - sys.stderr.write(firstException)
> - else:
> - raise firstException, None, traceback
> + if exc_type is None and undoExcInfo is not None:
> + raise undoExcInfo[0], undoExcInfo[1], undoExcInfo[2]
Should we return a False or True for __exit()__ to make the context
manager to re-raise or not?
>
> def defer(self, func, *args, **kwargs):
> self._finally.append((func, args, kwargs))
> diff --git a/tests/test_rollbackcontext.py b/tests/test_rollbackcontext.py
> new file mode 100644
> index 0000000..23b41c4
> --- /dev/null
> +++ b/tests/test_rollbackcontext.py
> @@ -0,0 +1,102 @@
> +#
> +# Project Kimchi
> +#
> +# Copyright IBM, Corp. 2013
> +#
> +# Authors:
> +# Zhou Zheng Sheng <zhshzhou at linux.vnet.ibm.com>
> +#
> +# This library is free software; you can redistribute it and/or
> +# modify it under the terms of the GNU Lesser General Public
> +# License as published by the Free Software Foundation; either
> +# version 2.1 of the License, or (at your option) any later version.
> +#
> +# This library is distributed in the hope that it will be useful,
> +# but WITHOUT ANY WARRANTY; without even the implied warranty of
> +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
> +# Lesser General Public License for more details.
> +#
> +# You should have received a copy of the GNU Lesser General Public
> +# License along with this library; if not, write to the Free Software
> +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
> +
> +import unittest
> +
> +from kimchi.rollbackcontext import RollbackContext
> +
> +
> +class FirstError(Exception):
> + '''A hypothetical exception to be raise in the test firstly.'''
> + pass
> +
> +
> +class SecondError(Exception):
> + '''A hypothetical exception to be raise in the test secondly.'''
> + pass
> +
> +
> +class RollbackContextTests(unittest.TestCase):
> +
> + def setUp(self):
> + self._counter = 0
> +
> + def _inc_counter(self):
> + self._counter += 1
> +
> + def _raise(self, exception=FirstError):
> + raise exception()
> +
> + def test_rollback(self):
> + with RollbackContext() as rollback:
> + rollback.prependDefer(self._inc_counter)
> + rollback.prependDefer(self._inc_counter)
> + self.assertEquals(self._counter, 2)
> +
> + def test_raise(self):
> + try:
> + with RollbackContext() as rollback:
> + rollback.prependDefer(self._inc_counter)
> + rollback.prependDefer(self._inc_counter)
> + raise FirstError()
> + rollback.prependDefer(self._inc_counter)
> + except FirstError:
> + # All undo before the FirstError should be run
> + self.assertEquals(self._counter, 2)
> + else:
> + self.fail('Should have raised FirstError')
> +
> + def test_raise_undo(self):
> + try:
> + with RollbackContext() as rollback:
> + rollback.prependDefer(self._inc_counter)
> + rollback.prependDefer(self._raise)
> + rollback.prependDefer(self._inc_counter)
> + except FirstError:
> + # All undo should be run
> + self.assertEquals(self._counter, 2)
> + else:
> + self.fail('Should have raised FirstError')
> +
> + def test_raise_prefer_original(self):
> + try:
> + with RollbackContext() as rollback:
> + rollback.prependDefer(self._raise, SecondError)
> + raise FirstError()
> + except FirstError:
> + pass
> + except SecondError:
> + self.fail('Should have preferred FirstError to SecondError')
> + else:
> + self.fail('Should have raised FirstError')
> +
> + def test_raise_prefer_first_undo(self):
> + try:
> + with RollbackContext() as rollback:
> + rollback.prependDefer(self._raise, SecondError)
> + rollback.prependDefer(self._raise, FirstError)
> + except FirstError:
> + pass
> + except SecondError:
> + self.fail('Should have preferred FirstError to SecondError')
> + else:
> + self.fail('Should have raised FirstError')
More information about the Kimchi-devel
mailing list