diff --git a/devlib/utils/misc.py b/devlib/utils/misc.py
index 54ba2b1..4d488b6 100644
--- a/devlib/utils/misc.py
+++ b/devlib/utils/misc.py
@@ -19,11 +19,13 @@ Miscellaneous functions that don't fit anywhere else.
 
 """
 from __future__ import division
+from contextlib import contextmanager
 from functools import partial, reduce
 from itertools import groupby
 from operator import itemgetter
 
 import ctypes
+import functools
 import logging
 import os
 import pkgutil
@@ -38,6 +40,11 @@ import wrapt
 import warnings
 
 
+try:
+    from contextlib import ExitStack
+except AttributeError:
+    from contextlib2 import ExitStack
+
 from past.builtins import basestring
 
 # pylint: disable=redefined-builtin
@@ -695,3 +702,19 @@ def memoized(wrapped, instance, args, kwargs):  # pylint: disable=unused-argumen
         return __memo_cache[id_string]
 
     return memoize_wrapper(*args, **kwargs)
+
+@contextmanager
+def batch_contextmanager(f, kwargs_list):
+    """
+    Return a context manager that will call the ``f`` callable with the keyword
+    arguments dict in the given list, in one go.
+
+    :param f: Callable expected to return a context manager.
+
+    :param kwargs_list: list of kwargs dictionaries to be used to call ``f``.
+    :type kwargs_list: list(dict)
+    """
+    with ExitStack() as stack:
+        for kwargs in kwargs_list:
+            stack.enter_context(f(**kwargs))
+        yield
diff --git a/setup.py b/setup.py
index 2c30895..df3377d 100644
--- a/setup.py
+++ b/setup.py
@@ -85,6 +85,7 @@ params = dict(
         'wrapt',  # Basic for construction of decorator functions
         'future', # Python 2-3 compatibility
         'enum34;python_version<"3.4"', # Enums for Python < 3.4
+        'contextlib2;python_version<"3.0"', # Python 3 contextlib backport for Python 2
         'numpy<=1.16.4; python_version<"3"',
         'numpy; python_version>="3"',
         'pandas<=0.24.2; python_version<"3"',