Source code for stepler.third_party.context
"""
--------------------------------
Custom context manager generator
--------------------------------
"""
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
# implied.
# See the License for the specific language governing permissions and
# limitations under the License.from functools import wraps
import functools
__all__ = ["context"]
class ContextGenerator(object):
"""Helper for @context decorator."""
def __init__(self, generator):
self._generator = generator
def __enter__(self):
try:
return next(self._generator)
except StopIteration:
raise RuntimeError("generator didn't yield")
def __exit__(self, ext_type, ext_val, ext_tb):
# Check for fixture finalization cancellation
if ext_type is GeneratorExit:
return
try:
next(self._generator)
except StopIteration:
pass
except Exception:
# TODO(gdyuldin): replace with raise from after moving to python3
if ext_type is None:
raise # finalization error if no root cause error inside cm
else:
raise RuntimeError("generator didn't stop")
if ext_type is not None:
return False
[docs]def context(func):
"""Decorator to make context manager from generator with guaranteed
finalization.
Note:
``contextlib.contextmanager`` doesn't guarantee context manager
finalization and requires usage of ``try-finally`` for that. But in
fixtures it needs to rid of ``try-finally`` and to guarantee context
manager finalization after ``yield``. This decorator makes that.
Example:
.. code:: python
@pytest.fixture
def create_server_context(server_steps):
@context
def _create_server_context(server_name, *args, **kwgs):
server = server_steps.create_server(server_name,
*args, **kwgs)
yield server
server_steps.delete_server(server)
return _create_server_context
See Also:
#. Exception inside context manager:
.. code:: python
@context
def x():
yield
with x():
raise Exception('error')
``Exception: error`` will be raised.
#. Exception in context manager finalization:
.. code:: python
@context
def x():
yield
raise Exception('final')
with x():
pass
``Exception: final`` will be raised.
#. Exceptions inside context manager and in finalization:
.. code:: python
@context
def x():
yield
raise Exception('final')
with x():
raise Exception('error')
``Exception: error`` will be raise as root cause.
"""
@functools.wraps(func)
def wrapper(*args, **kwgs):
return ContextGenerator(func(*args, **kwgs))
return wrapper