Merge "Move 'fill_iter' to 'iter_utils.fill'"

This commit is contained in:
Jenkins
2015-11-18 17:28:36 +00:00
committed by Gerrit Code Review
3 changed files with 111 additions and 25 deletions

View File

@@ -30,6 +30,51 @@ def forever_it():
class IterUtilsTest(test.TestCase):
def test_fill_empty(self):
self.assertEqual([], list(iter_utils.fill([1, 2, 3], 0)))
def test_bad_unique_seen(self):
iters = [
['a', 'b'],
2,
None,
]
self.assertRaises(ValueError,
iter_utils.unique_seen, *iters)
def test_unique_seen(self):
iters = [
['a', 'b'],
['a', 'c', 'd'],
['a', 'e', 'f'],
['f', 'm', 'n'],
]
self.assertEqual(['a', 'b', 'c', 'd', 'e', 'f', 'm', 'n'],
list(iter_utils.unique_seen(*iters)))
def test_bad_fill(self):
self.assertRaises(ValueError, iter_utils.fill, 2, 2)
def test_fill_many_empty(self):
result = list(iter_utils.fill(compat_range(0, 50), 500))
self.assertEqual(450, sum(1 for x in result if x is None))
self.assertEqual(50, sum(1 for x in result if x is not None))
def test_fill_custom_filler(self):
self.assertEqual("abcd",
"".join(iter_utils.fill("abc", 4, filler='d')))
def test_fill_less_needed(self):
self.assertEqual("ab", "".join(iter_utils.fill("abc", 2)))
def test_fill(self):
self.assertEqual([None, None], list(iter_utils.fill([], 2)))
self.assertEqual((None, None), tuple(iter_utils.fill([], 2)))
def test_bad_find_first_match(self):
self.assertRaises(ValueError,
iter_utils.find_first_match, 2, lambda v: False)
def test_find_first_match(self):
it = forever_it()
self.assertEqual(100, iter_utils.find_first_match(it,
@@ -40,6 +85,9 @@ class IterUtilsTest(test.TestCase):
self.assertIsNone(iter_utils.find_first_match(it,
lambda v: v == ''))
def test_bad_count(self):
self.assertRaises(ValueError, iter_utils.count, 2)
def test_count(self):
self.assertEqual(0, iter_utils.count([]))
self.assertEqual(1, iter_utils.count(['a']))
@@ -48,6 +96,9 @@ class IterUtilsTest(test.TestCase):
self.assertEqual(0, iter_utils.count(compat_range(0)))
self.assertEqual(0, iter_utils.count(compat_range(-1)))
def test_bad_while_is_not(self):
self.assertRaises(ValueError, iter_utils.while_is_not, 2, 'a')
def test_while_is_not(self):
it = iter(string.ascii_lowercase)
self.assertEqual(['a'],

View File

@@ -24,6 +24,7 @@ from oslo_utils import reflection
import six
from taskflow import exceptions as exc
from taskflow.utils import iter_utils
from taskflow.utils import mixins
from taskflow.utils import schema_utils as su
@@ -40,23 +41,6 @@ def _copy_exc_info(exc_info):
return (exc_type, copy.copy(exc_value), tb)
def _fill_iter(it, desired_len, filler=None):
"""Iterates over a provided iterator up to the desired length.
If the source iterator does not have enough values then the filler
value is yielded until the desired length is reached.
"""
count = 0
for value in it:
if count >= desired_len:
return
yield value
count += 1
while count < desired_len:
yield filler
count += 1
def _are_equal_exc_info_tuples(ei1, ei2):
if ei1 == ei2:
return True
@@ -444,7 +428,7 @@ class Failure(mixins.StrMixin):
# what the twisted people have done, see for example
# twisted-13.0.0/twisted/python/failure.py#L89 for how they
# created a fake traceback object...
self._exc_info = tuple(_fill_iter(dct['exc_info'], 3))
self._exc_info = tuple(iter_utils.fill(dct['exc_info'], 3))
else:
self._exc_info = None
causes = dct.get('causes')

View File

@@ -16,10 +16,45 @@
# License for the specific language governing permissions and limitations
# under the License.
import collections
import itertools
import six
from six.moves import range as compat_range
def _ensure_iterable(func):
@six.wraps(func)
def wrapper(it, *args, **kwargs):
if not isinstance(it, collections.Iterable):
raise ValueError("Iterable expected, but '%s' is not"
" iterable" % it)
return func(it, *args, **kwargs)
return wrapper
@_ensure_iterable
def fill(it, desired_len, filler=None):
"""Iterates over a provided iterator up to the desired length.
If the source iterator does not have enough values then the filler
value is yielded until the desired length is reached.
"""
if desired_len > 0:
count = 0
for value in it:
yield value
count += 1
if count >= desired_len:
return
while count < desired_len:
yield filler
count += 1
@_ensure_iterable
def count(it):
"""Returns how many values in the iterator (depletes the iterator)."""
return sum(1 for _value in it)
@@ -27,15 +62,30 @@ def count(it):
def unique_seen(it, *its):
"""Yields unique values from iterator(s) (and retains order)."""
seen = set()
for value in itertools.chain(it, *its):
if value in seen:
continue
else:
yield value
seen.add(value)
def _gen_it(all_its):
# NOTE(harlowja): Generation is delayed so that validation
# can happen before generation/iteration... (instead of
# during generation/iteration)
seen = set()
while all_its:
it = all_its.popleft()
for value in it:
if value not in seen:
yield value
seen.add(value)
all_its = collections.deque([it])
if its:
all_its.extend(its)
for it in all_its:
if not isinstance(it, collections.Iterable):
raise ValueError("Iterable expected, but '%s' is"
" not iterable" % it)
return _gen_it(all_its)
@_ensure_iterable
def find_first_match(it, matcher, not_found_value=None):
"""Searches iterator for first value that matcher callback returns true."""
for value in it:
@@ -44,6 +94,7 @@ def find_first_match(it, matcher, not_found_value=None):
return not_found_value
@_ensure_iterable
def while_is_not(it, stop_value):
"""Yields given values from iterator until stop value is passed.