mirror of
https://github.com/jellyfin/jellyfin-kodi.git
synced 2025-06-25 17:40:31 +00:00
Update webservice with cherrypy
Fix playback issues that was causing Kodi to hang up
This commit is contained in:
parent
b2bc90cb06
commit
158a736360
164 changed files with 42855 additions and 174 deletions
2
libraries/more_itertools/__init__.py
Normal file
2
libraries/more_itertools/__init__.py
Normal file
|
@ -0,0 +1,2 @@
|
|||
from more_itertools.more import * # noqa
|
||||
from more_itertools.recipes import * # noqa
|
2211
libraries/more_itertools/more.py
Normal file
2211
libraries/more_itertools/more.py
Normal file
File diff suppressed because it is too large
Load diff
565
libraries/more_itertools/recipes.py
Normal file
565
libraries/more_itertools/recipes.py
Normal file
|
@ -0,0 +1,565 @@
|
|||
"""Imported from the recipes section of the itertools documentation.
|
||||
|
||||
All functions taken from the recipes section of the itertools library docs
|
||||
[1]_.
|
||||
Some backward-compatible usability improvements have been made.
|
||||
|
||||
.. [1] http://docs.python.org/library/itertools.html#recipes
|
||||
|
||||
"""
|
||||
from collections import deque
|
||||
from itertools import (
|
||||
chain, combinations, count, cycle, groupby, islice, repeat, starmap, tee
|
||||
)
|
||||
import operator
|
||||
from random import randrange, sample, choice
|
||||
|
||||
from six import PY2
|
||||
from six.moves import filter, filterfalse, map, range, zip, zip_longest
|
||||
|
||||
__all__ = [
|
||||
'accumulate',
|
||||
'all_equal',
|
||||
'consume',
|
||||
'dotproduct',
|
||||
'first_true',
|
||||
'flatten',
|
||||
'grouper',
|
||||
'iter_except',
|
||||
'ncycles',
|
||||
'nth',
|
||||
'nth_combination',
|
||||
'padnone',
|
||||
'pairwise',
|
||||
'partition',
|
||||
'powerset',
|
||||
'prepend',
|
||||
'quantify',
|
||||
'random_combination_with_replacement',
|
||||
'random_combination',
|
||||
'random_permutation',
|
||||
'random_product',
|
||||
'repeatfunc',
|
||||
'roundrobin',
|
||||
'tabulate',
|
||||
'tail',
|
||||
'take',
|
||||
'unique_everseen',
|
||||
'unique_justseen',
|
||||
]
|
||||
|
||||
|
||||
def accumulate(iterable, func=operator.add):
|
||||
"""
|
||||
Return an iterator whose items are the accumulated results of a function
|
||||
(specified by the optional *func* argument) that takes two arguments.
|
||||
By default, returns accumulated sums with :func:`operator.add`.
|
||||
|
||||
>>> list(accumulate([1, 2, 3, 4, 5])) # Running sum
|
||||
[1, 3, 6, 10, 15]
|
||||
>>> list(accumulate([1, 2, 3], func=operator.mul)) # Running product
|
||||
[1, 2, 6]
|
||||
>>> list(accumulate([0, 1, -1, 2, 3, 2], func=max)) # Running maximum
|
||||
[0, 1, 1, 2, 3, 3]
|
||||
|
||||
This function is available in the ``itertools`` module for Python 3.2 and
|
||||
greater.
|
||||
|
||||
"""
|
||||
it = iter(iterable)
|
||||
try:
|
||||
total = next(it)
|
||||
except StopIteration:
|
||||
return
|
||||
else:
|
||||
yield total
|
||||
|
||||
for element in it:
|
||||
total = func(total, element)
|
||||
yield total
|
||||
|
||||
|
||||
def take(n, iterable):
|
||||
"""Return first *n* items of the iterable as a list.
|
||||
|
||||
>>> take(3, range(10))
|
||||
[0, 1, 2]
|
||||
>>> take(5, range(3))
|
||||
[0, 1, 2]
|
||||
|
||||
Effectively a short replacement for ``next`` based iterator consumption
|
||||
when you want more than one item, but less than the whole iterator.
|
||||
|
||||
"""
|
||||
return list(islice(iterable, n))
|
||||
|
||||
|
||||
def tabulate(function, start=0):
|
||||
"""Return an iterator over the results of ``func(start)``,
|
||||
``func(start + 1)``, ``func(start + 2)``...
|
||||
|
||||
*func* should be a function that accepts one integer argument.
|
||||
|
||||
If *start* is not specified it defaults to 0. It will be incremented each
|
||||
time the iterator is advanced.
|
||||
|
||||
>>> square = lambda x: x ** 2
|
||||
>>> iterator = tabulate(square, -3)
|
||||
>>> take(4, iterator)
|
||||
[9, 4, 1, 0]
|
||||
|
||||
"""
|
||||
return map(function, count(start))
|
||||
|
||||
|
||||
def tail(n, iterable):
|
||||
"""Return an iterator over the last *n* items of *iterable*.
|
||||
|
||||
>>> t = tail(3, 'ABCDEFG')
|
||||
>>> list(t)
|
||||
['E', 'F', 'G']
|
||||
|
||||
"""
|
||||
return iter(deque(iterable, maxlen=n))
|
||||
|
||||
|
||||
def consume(iterator, n=None):
|
||||
"""Advance *iterable* by *n* steps. If *n* is ``None``, consume it
|
||||
entirely.
|
||||
|
||||
Efficiently exhausts an iterator without returning values. Defaults to
|
||||
consuming the whole iterator, but an optional second argument may be
|
||||
provided to limit consumption.
|
||||
|
||||
>>> i = (x for x in range(10))
|
||||
>>> next(i)
|
||||
0
|
||||
>>> consume(i, 3)
|
||||
>>> next(i)
|
||||
4
|
||||
>>> consume(i)
|
||||
>>> next(i)
|
||||
Traceback (most recent call last):
|
||||
File "<stdin>", line 1, in <module>
|
||||
StopIteration
|
||||
|
||||
If the iterator has fewer items remaining than the provided limit, the
|
||||
whole iterator will be consumed.
|
||||
|
||||
>>> i = (x for x in range(3))
|
||||
>>> consume(i, 5)
|
||||
>>> next(i)
|
||||
Traceback (most recent call last):
|
||||
File "<stdin>", line 1, in <module>
|
||||
StopIteration
|
||||
|
||||
"""
|
||||
# Use functions that consume iterators at C speed.
|
||||
if n is None:
|
||||
# feed the entire iterator into a zero-length deque
|
||||
deque(iterator, maxlen=0)
|
||||
else:
|
||||
# advance to the empty slice starting at position n
|
||||
next(islice(iterator, n, n), None)
|
||||
|
||||
|
||||
def nth(iterable, n, default=None):
|
||||
"""Returns the nth item or a default value.
|
||||
|
||||
>>> l = range(10)
|
||||
>>> nth(l, 3)
|
||||
3
|
||||
>>> nth(l, 20, "zebra")
|
||||
'zebra'
|
||||
|
||||
"""
|
||||
return next(islice(iterable, n, None), default)
|
||||
|
||||
|
||||
def all_equal(iterable):
|
||||
"""
|
||||
Returns ``True`` if all the elements are equal to each other.
|
||||
|
||||
>>> all_equal('aaaa')
|
||||
True
|
||||
>>> all_equal('aaab')
|
||||
False
|
||||
|
||||
"""
|
||||
g = groupby(iterable)
|
||||
return next(g, True) and not next(g, False)
|
||||
|
||||
|
||||
def quantify(iterable, pred=bool):
|
||||
"""Return the how many times the predicate is true.
|
||||
|
||||
>>> quantify([True, False, True])
|
||||
2
|
||||
|
||||
"""
|
||||
return sum(map(pred, iterable))
|
||||
|
||||
|
||||
def padnone(iterable):
|
||||
"""Returns the sequence of elements and then returns ``None`` indefinitely.
|
||||
|
||||
>>> take(5, padnone(range(3)))
|
||||
[0, 1, 2, None, None]
|
||||
|
||||
Useful for emulating the behavior of the built-in :func:`map` function.
|
||||
|
||||
See also :func:`padded`.
|
||||
|
||||
"""
|
||||
return chain(iterable, repeat(None))
|
||||
|
||||
|
||||
def ncycles(iterable, n):
|
||||
"""Returns the sequence elements *n* times
|
||||
|
||||
>>> list(ncycles(["a", "b"], 3))
|
||||
['a', 'b', 'a', 'b', 'a', 'b']
|
||||
|
||||
"""
|
||||
return chain.from_iterable(repeat(tuple(iterable), n))
|
||||
|
||||
|
||||
def dotproduct(vec1, vec2):
|
||||
"""Returns the dot product of the two iterables.
|
||||
|
||||
>>> dotproduct([10, 10], [20, 20])
|
||||
400
|
||||
|
||||
"""
|
||||
return sum(map(operator.mul, vec1, vec2))
|
||||
|
||||
|
||||
def flatten(listOfLists):
|
||||
"""Return an iterator flattening one level of nesting in a list of lists.
|
||||
|
||||
>>> list(flatten([[0, 1], [2, 3]]))
|
||||
[0, 1, 2, 3]
|
||||
|
||||
See also :func:`collapse`, which can flatten multiple levels of nesting.
|
||||
|
||||
"""
|
||||
return chain.from_iterable(listOfLists)
|
||||
|
||||
|
||||
def repeatfunc(func, times=None, *args):
|
||||
"""Call *func* with *args* repeatedly, returning an iterable over the
|
||||
results.
|
||||
|
||||
If *times* is specified, the iterable will terminate after that many
|
||||
repetitions:
|
||||
|
||||
>>> from operator import add
|
||||
>>> times = 4
|
||||
>>> args = 3, 5
|
||||
>>> list(repeatfunc(add, times, *args))
|
||||
[8, 8, 8, 8]
|
||||
|
||||
If *times* is ``None`` the iterable will not terminate:
|
||||
|
||||
>>> from random import randrange
|
||||
>>> times = None
|
||||
>>> args = 1, 11
|
||||
>>> take(6, repeatfunc(randrange, times, *args)) # doctest:+SKIP
|
||||
[2, 4, 8, 1, 8, 4]
|
||||
|
||||
"""
|
||||
if times is None:
|
||||
return starmap(func, repeat(args))
|
||||
return starmap(func, repeat(args, times))
|
||||
|
||||
|
||||
def pairwise(iterable):
|
||||
"""Returns an iterator of paired items, overlapping, from the original
|
||||
|
||||
>>> take(4, pairwise(count()))
|
||||
[(0, 1), (1, 2), (2, 3), (3, 4)]
|
||||
|
||||
"""
|
||||
a, b = tee(iterable)
|
||||
next(b, None)
|
||||
return zip(a, b)
|
||||
|
||||
|
||||
def grouper(n, iterable, fillvalue=None):
|
||||
"""Collect data into fixed-length chunks or blocks.
|
||||
|
||||
>>> list(grouper(3, 'ABCDEFG', 'x'))
|
||||
[('A', 'B', 'C'), ('D', 'E', 'F'), ('G', 'x', 'x')]
|
||||
|
||||
"""
|
||||
args = [iter(iterable)] * n
|
||||
return zip_longest(fillvalue=fillvalue, *args)
|
||||
|
||||
|
||||
def roundrobin(*iterables):
|
||||
"""Yields an item from each iterable, alternating between them.
|
||||
|
||||
>>> list(roundrobin('ABC', 'D', 'EF'))
|
||||
['A', 'D', 'E', 'B', 'F', 'C']
|
||||
|
||||
This function produces the same output as :func:`interleave_longest`, but
|
||||
may perform better for some inputs (in particular when the number of
|
||||
iterables is small).
|
||||
|
||||
"""
|
||||
# Recipe credited to George Sakkis
|
||||
pending = len(iterables)
|
||||
if PY2:
|
||||
nexts = cycle(iter(it).next for it in iterables)
|
||||
else:
|
||||
nexts = cycle(iter(it).__next__ for it in iterables)
|
||||
while pending:
|
||||
try:
|
||||
for next in nexts:
|
||||
yield next()
|
||||
except StopIteration:
|
||||
pending -= 1
|
||||
nexts = cycle(islice(nexts, pending))
|
||||
|
||||
|
||||
def partition(pred, iterable):
|
||||
"""
|
||||
Returns a 2-tuple of iterables derived from the input iterable.
|
||||
The first yields the items that have ``pred(item) == False``.
|
||||
The second yields the items that have ``pred(item) == True``.
|
||||
|
||||
>>> is_odd = lambda x: x % 2 != 0
|
||||
>>> iterable = range(10)
|
||||
>>> even_items, odd_items = partition(is_odd, iterable)
|
||||
>>> list(even_items), list(odd_items)
|
||||
([0, 2, 4, 6, 8], [1, 3, 5, 7, 9])
|
||||
|
||||
"""
|
||||
# partition(is_odd, range(10)) --> 0 2 4 6 8 and 1 3 5 7 9
|
||||
t1, t2 = tee(iterable)
|
||||
return filterfalse(pred, t1), filter(pred, t2)
|
||||
|
||||
|
||||
def powerset(iterable):
|
||||
"""Yields all possible subsets of the iterable.
|
||||
|
||||
>>> list(powerset([1,2,3]))
|
||||
[(), (1,), (2,), (3,), (1, 2), (1, 3), (2, 3), (1, 2, 3)]
|
||||
|
||||
"""
|
||||
s = list(iterable)
|
||||
return chain.from_iterable(combinations(s, r) for r in range(len(s) + 1))
|
||||
|
||||
|
||||
def unique_everseen(iterable, key=None):
|
||||
"""
|
||||
Yield unique elements, preserving order.
|
||||
|
||||
>>> list(unique_everseen('AAAABBBCCDAABBB'))
|
||||
['A', 'B', 'C', 'D']
|
||||
>>> list(unique_everseen('ABBCcAD', str.lower))
|
||||
['A', 'B', 'C', 'D']
|
||||
|
||||
Sequences with a mix of hashable and unhashable items can be used.
|
||||
The function will be slower (i.e., `O(n^2)`) for unhashable items.
|
||||
|
||||
"""
|
||||
seenset = set()
|
||||
seenset_add = seenset.add
|
||||
seenlist = []
|
||||
seenlist_add = seenlist.append
|
||||
if key is None:
|
||||
for element in iterable:
|
||||
try:
|
||||
if element not in seenset:
|
||||
seenset_add(element)
|
||||
yield element
|
||||
except TypeError:
|
||||
if element not in seenlist:
|
||||
seenlist_add(element)
|
||||
yield element
|
||||
else:
|
||||
for element in iterable:
|
||||
k = key(element)
|
||||
try:
|
||||
if k not in seenset:
|
||||
seenset_add(k)
|
||||
yield element
|
||||
except TypeError:
|
||||
if k not in seenlist:
|
||||
seenlist_add(k)
|
||||
yield element
|
||||
|
||||
|
||||
def unique_justseen(iterable, key=None):
|
||||
"""Yields elements in order, ignoring serial duplicates
|
||||
|
||||
>>> list(unique_justseen('AAAABBBCCDAABBB'))
|
||||
['A', 'B', 'C', 'D', 'A', 'B']
|
||||
>>> list(unique_justseen('ABBCcAD', str.lower))
|
||||
['A', 'B', 'C', 'A', 'D']
|
||||
|
||||
"""
|
||||
return map(next, map(operator.itemgetter(1), groupby(iterable, key)))
|
||||
|
||||
|
||||
def iter_except(func, exception, first=None):
|
||||
"""Yields results from a function repeatedly until an exception is raised.
|
||||
|
||||
Converts a call-until-exception interface to an iterator interface.
|
||||
Like ``iter(func, sentinel)``, but uses an exception instead of a sentinel
|
||||
to end the loop.
|
||||
|
||||
>>> l = [0, 1, 2]
|
||||
>>> list(iter_except(l.pop, IndexError))
|
||||
[2, 1, 0]
|
||||
|
||||
"""
|
||||
try:
|
||||
if first is not None:
|
||||
yield first()
|
||||
while 1:
|
||||
yield func()
|
||||
except exception:
|
||||
pass
|
||||
|
||||
|
||||
def first_true(iterable, default=False, pred=None):
|
||||
"""
|
||||
Returns the first true value in the iterable.
|
||||
|
||||
If no true value is found, returns *default*
|
||||
|
||||
If *pred* is not None, returns the first item for which
|
||||
``pred(item) == True`` .
|
||||
|
||||
>>> first_true(range(10))
|
||||
1
|
||||
>>> first_true(range(10), pred=lambda x: x > 5)
|
||||
6
|
||||
>>> first_true(range(10), default='missing', pred=lambda x: x > 9)
|
||||
'missing'
|
||||
|
||||
"""
|
||||
return next(filter(pred, iterable), default)
|
||||
|
||||
|
||||
def random_product(*args, **kwds):
|
||||
"""Draw an item at random from each of the input iterables.
|
||||
|
||||
>>> random_product('abc', range(4), 'XYZ') # doctest:+SKIP
|
||||
('c', 3, 'Z')
|
||||
|
||||
If *repeat* is provided as a keyword argument, that many items will be
|
||||
drawn from each iterable.
|
||||
|
||||
>>> random_product('abcd', range(4), repeat=2) # doctest:+SKIP
|
||||
('a', 2, 'd', 3)
|
||||
|
||||
This equivalent to taking a random selection from
|
||||
``itertools.product(*args, **kwarg)``.
|
||||
|
||||
"""
|
||||
pools = [tuple(pool) for pool in args] * kwds.get('repeat', 1)
|
||||
return tuple(choice(pool) for pool in pools)
|
||||
|
||||
|
||||
def random_permutation(iterable, r=None):
|
||||
"""Return a random *r* length permutation of the elements in *iterable*.
|
||||
|
||||
If *r* is not specified or is ``None``, then *r* defaults to the length of
|
||||
*iterable*.
|
||||
|
||||
>>> random_permutation(range(5)) # doctest:+SKIP
|
||||
(3, 4, 0, 1, 2)
|
||||
|
||||
This equivalent to taking a random selection from
|
||||
``itertools.permutations(iterable, r)``.
|
||||
|
||||
"""
|
||||
pool = tuple(iterable)
|
||||
r = len(pool) if r is None else r
|
||||
return tuple(sample(pool, r))
|
||||
|
||||
|
||||
def random_combination(iterable, r):
|
||||
"""Return a random *r* length subsequence of the elements in *iterable*.
|
||||
|
||||
>>> random_combination(range(5), 3) # doctest:+SKIP
|
||||
(2, 3, 4)
|
||||
|
||||
This equivalent to taking a random selection from
|
||||
``itertools.combinations(iterable, r)``.
|
||||
|
||||
"""
|
||||
pool = tuple(iterable)
|
||||
n = len(pool)
|
||||
indices = sorted(sample(range(n), r))
|
||||
return tuple(pool[i] for i in indices)
|
||||
|
||||
|
||||
def random_combination_with_replacement(iterable, r):
|
||||
"""Return a random *r* length subsequence of elements in *iterable*,
|
||||
allowing individual elements to be repeated.
|
||||
|
||||
>>> random_combination_with_replacement(range(3), 5) # doctest:+SKIP
|
||||
(0, 0, 1, 2, 2)
|
||||
|
||||
This equivalent to taking a random selection from
|
||||
``itertools.combinations_with_replacement(iterable, r)``.
|
||||
|
||||
"""
|
||||
pool = tuple(iterable)
|
||||
n = len(pool)
|
||||
indices = sorted(randrange(n) for i in range(r))
|
||||
return tuple(pool[i] for i in indices)
|
||||
|
||||
|
||||
def nth_combination(iterable, r, index):
|
||||
"""Equivalent to ``list(combinations(iterable, r))[index]``.
|
||||
|
||||
The subsequences of *iterable* that are of length *r* can be ordered
|
||||
lexicographically. :func:`nth_combination` computes the subsequence at
|
||||
sort position *index* directly, without computing the previous
|
||||
subsequences.
|
||||
|
||||
"""
|
||||
pool = tuple(iterable)
|
||||
n = len(pool)
|
||||
if (r < 0) or (r > n):
|
||||
raise ValueError
|
||||
|
||||
c = 1
|
||||
k = min(r, n - r)
|
||||
for i in range(1, k + 1):
|
||||
c = c * (n - k + i) // i
|
||||
|
||||
if index < 0:
|
||||
index += c
|
||||
|
||||
if (index < 0) or (index >= c):
|
||||
raise IndexError
|
||||
|
||||
result = []
|
||||
while r:
|
||||
c, n, r = c * r // n, n - 1, r - 1
|
||||
while index >= c:
|
||||
index -= c
|
||||
c, n = c * (n - r) // n, n - 1
|
||||
result.append(pool[-1 - n])
|
||||
|
||||
return tuple(result)
|
||||
|
||||
|
||||
def prepend(value, iterator):
|
||||
"""Yield *value*, followed by the elements in *iterator*.
|
||||
|
||||
>>> value = '0'
|
||||
>>> iterator = ['1', '2', '3']
|
||||
>>> list(prepend(value, iterator))
|
||||
['0', '1', '2', '3']
|
||||
|
||||
To prepend multiple values, see :func:`itertools.chain`.
|
||||
|
||||
"""
|
||||
return chain([value], iterator)
|
0
libraries/more_itertools/tests/__init__.py
Normal file
0
libraries/more_itertools/tests/__init__.py
Normal file
2074
libraries/more_itertools/tests/test_more.py
Normal file
2074
libraries/more_itertools/tests/test_more.py
Normal file
File diff suppressed because it is too large
Load diff
616
libraries/more_itertools/tests/test_recipes.py
Normal file
616
libraries/more_itertools/tests/test_recipes.py
Normal file
|
@ -0,0 +1,616 @@
|
|||
from doctest import DocTestSuite
|
||||
from unittest import TestCase
|
||||
|
||||
from itertools import combinations
|
||||
from six.moves import range
|
||||
|
||||
import more_itertools as mi
|
||||
|
||||
|
||||
def load_tests(loader, tests, ignore):
|
||||
# Add the doctests
|
||||
tests.addTests(DocTestSuite('more_itertools.recipes'))
|
||||
return tests
|
||||
|
||||
|
||||
class AccumulateTests(TestCase):
|
||||
"""Tests for ``accumulate()``"""
|
||||
|
||||
def test_empty(self):
|
||||
"""Test that an empty input returns an empty output"""
|
||||
self.assertEqual(list(mi.accumulate([])), [])
|
||||
|
||||
def test_default(self):
|
||||
"""Test accumulate with the default function (addition)"""
|
||||
self.assertEqual(list(mi.accumulate([1, 2, 3])), [1, 3, 6])
|
||||
|
||||
def test_bogus_function(self):
|
||||
"""Test accumulate with an invalid function"""
|
||||
with self.assertRaises(TypeError):
|
||||
list(mi.accumulate([1, 2, 3], func=lambda x: x))
|
||||
|
||||
def test_custom_function(self):
|
||||
"""Test accumulate with a custom function"""
|
||||
self.assertEqual(
|
||||
list(mi.accumulate((1, 2, 3, 2, 1), func=max)), [1, 2, 3, 3, 3]
|
||||
)
|
||||
|
||||
|
||||
class TakeTests(TestCase):
|
||||
"""Tests for ``take()``"""
|
||||
|
||||
def test_simple_take(self):
|
||||
"""Test basic usage"""
|
||||
t = mi.take(5, range(10))
|
||||
self.assertEqual(t, [0, 1, 2, 3, 4])
|
||||
|
||||
def test_null_take(self):
|
||||
"""Check the null case"""
|
||||
t = mi.take(0, range(10))
|
||||
self.assertEqual(t, [])
|
||||
|
||||
def test_negative_take(self):
|
||||
"""Make sure taking negative items results in a ValueError"""
|
||||
self.assertRaises(ValueError, lambda: mi.take(-3, range(10)))
|
||||
|
||||
def test_take_too_much(self):
|
||||
"""Taking more than an iterator has remaining should return what the
|
||||
iterator has remaining.
|
||||
|
||||
"""
|
||||
t = mi.take(10, range(5))
|
||||
self.assertEqual(t, [0, 1, 2, 3, 4])
|
||||
|
||||
|
||||
class TabulateTests(TestCase):
|
||||
"""Tests for ``tabulate()``"""
|
||||
|
||||
def test_simple_tabulate(self):
|
||||
"""Test the happy path"""
|
||||
t = mi.tabulate(lambda x: x)
|
||||
f = tuple([next(t) for _ in range(3)])
|
||||
self.assertEqual(f, (0, 1, 2))
|
||||
|
||||
def test_count(self):
|
||||
"""Ensure tabulate accepts specific count"""
|
||||
t = mi.tabulate(lambda x: 2 * x, -1)
|
||||
f = (next(t), next(t), next(t))
|
||||
self.assertEqual(f, (-2, 0, 2))
|
||||
|
||||
|
||||
class TailTests(TestCase):
|
||||
"""Tests for ``tail()``"""
|
||||
|
||||
def test_greater(self):
|
||||
"""Length of iterable is greather than requested tail"""
|
||||
self.assertEqual(list(mi.tail(3, 'ABCDEFG')), ['E', 'F', 'G'])
|
||||
|
||||
def test_equal(self):
|
||||
"""Length of iterable is equal to the requested tail"""
|
||||
self.assertEqual(
|
||||
list(mi.tail(7, 'ABCDEFG')), ['A', 'B', 'C', 'D', 'E', 'F', 'G']
|
||||
)
|
||||
|
||||
def test_less(self):
|
||||
"""Length of iterable is less than requested tail"""
|
||||
self.assertEqual(
|
||||
list(mi.tail(8, 'ABCDEFG')), ['A', 'B', 'C', 'D', 'E', 'F', 'G']
|
||||
)
|
||||
|
||||
|
||||
class ConsumeTests(TestCase):
|
||||
"""Tests for ``consume()``"""
|
||||
|
||||
def test_sanity(self):
|
||||
"""Test basic functionality"""
|
||||
r = (x for x in range(10))
|
||||
mi.consume(r, 3)
|
||||
self.assertEqual(3, next(r))
|
||||
|
||||
def test_null_consume(self):
|
||||
"""Check the null case"""
|
||||
r = (x for x in range(10))
|
||||
mi.consume(r, 0)
|
||||
self.assertEqual(0, next(r))
|
||||
|
||||
def test_negative_consume(self):
|
||||
"""Check that negative consumsion throws an error"""
|
||||
r = (x for x in range(10))
|
||||
self.assertRaises(ValueError, lambda: mi.consume(r, -1))
|
||||
|
||||
def test_total_consume(self):
|
||||
"""Check that iterator is totally consumed by default"""
|
||||
r = (x for x in range(10))
|
||||
mi.consume(r)
|
||||
self.assertRaises(StopIteration, lambda: next(r))
|
||||
|
||||
|
||||
class NthTests(TestCase):
|
||||
"""Tests for ``nth()``"""
|
||||
|
||||
def test_basic(self):
|
||||
"""Make sure the nth item is returned"""
|
||||
l = range(10)
|
||||
for i, v in enumerate(l):
|
||||
self.assertEqual(mi.nth(l, i), v)
|
||||
|
||||
def test_default(self):
|
||||
"""Ensure a default value is returned when nth item not found"""
|
||||
l = range(3)
|
||||
self.assertEqual(mi.nth(l, 100, "zebra"), "zebra")
|
||||
|
||||
def test_negative_item_raises(self):
|
||||
"""Ensure asking for a negative item raises an exception"""
|
||||
self.assertRaises(ValueError, lambda: mi.nth(range(10), -3))
|
||||
|
||||
|
||||
class AllEqualTests(TestCase):
|
||||
"""Tests for ``all_equal()``"""
|
||||
|
||||
def test_true(self):
|
||||
"""Everything is equal"""
|
||||
self.assertTrue(mi.all_equal('aaaaaa'))
|
||||
self.assertTrue(mi.all_equal([0, 0, 0, 0]))
|
||||
|
||||
def test_false(self):
|
||||
"""Not everything is equal"""
|
||||
self.assertFalse(mi.all_equal('aaaaab'))
|
||||
self.assertFalse(mi.all_equal([0, 0, 0, 1]))
|
||||
|
||||
def test_tricky(self):
|
||||
"""Not everything is identical, but everything is equal"""
|
||||
items = [1, complex(1, 0), 1.0]
|
||||
self.assertTrue(mi.all_equal(items))
|
||||
|
||||
def test_empty(self):
|
||||
"""Return True if the iterable is empty"""
|
||||
self.assertTrue(mi.all_equal(''))
|
||||
self.assertTrue(mi.all_equal([]))
|
||||
|
||||
def test_one(self):
|
||||
"""Return True if the iterable is singular"""
|
||||
self.assertTrue(mi.all_equal('0'))
|
||||
self.assertTrue(mi.all_equal([0]))
|
||||
|
||||
|
||||
class QuantifyTests(TestCase):
|
||||
"""Tests for ``quantify()``"""
|
||||
|
||||
def test_happy_path(self):
|
||||
"""Make sure True count is returned"""
|
||||
q = [True, False, True]
|
||||
self.assertEqual(mi.quantify(q), 2)
|
||||
|
||||
def test_custom_predicate(self):
|
||||
"""Ensure non-default predicates return as expected"""
|
||||
q = range(10)
|
||||
self.assertEqual(mi.quantify(q, lambda x: x % 2 == 0), 5)
|
||||
|
||||
|
||||
class PadnoneTests(TestCase):
|
||||
"""Tests for ``padnone()``"""
|
||||
|
||||
def test_happy_path(self):
|
||||
"""wrapper iterator should return None indefinitely"""
|
||||
r = range(2)
|
||||
p = mi.padnone(r)
|
||||
self.assertEqual([0, 1, None, None], [next(p) for _ in range(4)])
|
||||
|
||||
|
||||
class NcyclesTests(TestCase):
|
||||
"""Tests for ``nyclces()``"""
|
||||
|
||||
def test_happy_path(self):
|
||||
"""cycle a sequence three times"""
|
||||
r = ["a", "b", "c"]
|
||||
n = mi.ncycles(r, 3)
|
||||
self.assertEqual(
|
||||
["a", "b", "c", "a", "b", "c", "a", "b", "c"],
|
||||
list(n)
|
||||
)
|
||||
|
||||
def test_null_case(self):
|
||||
"""asking for 0 cycles should return an empty iterator"""
|
||||
n = mi.ncycles(range(100), 0)
|
||||
self.assertRaises(StopIteration, lambda: next(n))
|
||||
|
||||
def test_pathalogical_case(self):
|
||||
"""asking for negative cycles should return an empty iterator"""
|
||||
n = mi.ncycles(range(100), -10)
|
||||
self.assertRaises(StopIteration, lambda: next(n))
|
||||
|
||||
|
||||
class DotproductTests(TestCase):
|
||||
"""Tests for ``dotproduct()``'"""
|
||||
|
||||
def test_happy_path(self):
|
||||
"""simple dotproduct example"""
|
||||
self.assertEqual(400, mi.dotproduct([10, 10], [20, 20]))
|
||||
|
||||
|
||||
class FlattenTests(TestCase):
|
||||
"""Tests for ``flatten()``"""
|
||||
|
||||
def test_basic_usage(self):
|
||||
"""ensure list of lists is flattened one level"""
|
||||
f = [[0, 1, 2], [3, 4, 5]]
|
||||
self.assertEqual(list(range(6)), list(mi.flatten(f)))
|
||||
|
||||
def test_single_level(self):
|
||||
"""ensure list of lists is flattened only one level"""
|
||||
f = [[0, [1, 2]], [[3, 4], 5]]
|
||||
self.assertEqual([0, [1, 2], [3, 4], 5], list(mi.flatten(f)))
|
||||
|
||||
|
||||
class RepeatfuncTests(TestCase):
|
||||
"""Tests for ``repeatfunc()``"""
|
||||
|
||||
def test_simple_repeat(self):
|
||||
"""test simple repeated functions"""
|
||||
r = mi.repeatfunc(lambda: 5)
|
||||
self.assertEqual([5, 5, 5, 5, 5], [next(r) for _ in range(5)])
|
||||
|
||||
def test_finite_repeat(self):
|
||||
"""ensure limited repeat when times is provided"""
|
||||
r = mi.repeatfunc(lambda: 5, times=5)
|
||||
self.assertEqual([5, 5, 5, 5, 5], list(r))
|
||||
|
||||
def test_added_arguments(self):
|
||||
"""ensure arguments are applied to the function"""
|
||||
r = mi.repeatfunc(lambda x: x, 2, 3)
|
||||
self.assertEqual([3, 3], list(r))
|
||||
|
||||
def test_null_times(self):
|
||||
"""repeat 0 should return an empty iterator"""
|
||||
r = mi.repeatfunc(range, 0, 3)
|
||||
self.assertRaises(StopIteration, lambda: next(r))
|
||||
|
||||
|
||||
class PairwiseTests(TestCase):
|
||||
"""Tests for ``pairwise()``"""
|
||||
|
||||
def test_base_case(self):
|
||||
"""ensure an iterable will return pairwise"""
|
||||
p = mi.pairwise([1, 2, 3])
|
||||
self.assertEqual([(1, 2), (2, 3)], list(p))
|
||||
|
||||
def test_short_case(self):
|
||||
"""ensure an empty iterator if there's not enough values to pair"""
|
||||
p = mi.pairwise("a")
|
||||
self.assertRaises(StopIteration, lambda: next(p))
|
||||
|
||||
|
||||
class GrouperTests(TestCase):
|
||||
"""Tests for ``grouper()``"""
|
||||
|
||||
def test_even(self):
|
||||
"""Test when group size divides evenly into the length of
|
||||
the iterable.
|
||||
|
||||
"""
|
||||
self.assertEqual(
|
||||
list(mi.grouper(3, 'ABCDEF')), [('A', 'B', 'C'), ('D', 'E', 'F')]
|
||||
)
|
||||
|
||||
def test_odd(self):
|
||||
"""Test when group size does not divide evenly into the length of the
|
||||
iterable.
|
||||
|
||||
"""
|
||||
self.assertEqual(
|
||||
list(mi.grouper(3, 'ABCDE')), [('A', 'B', 'C'), ('D', 'E', None)]
|
||||
)
|
||||
|
||||
def test_fill_value(self):
|
||||
"""Test that the fill value is used to pad the final group"""
|
||||
self.assertEqual(
|
||||
list(mi.grouper(3, 'ABCDE', 'x')),
|
||||
[('A', 'B', 'C'), ('D', 'E', 'x')]
|
||||
)
|
||||
|
||||
|
||||
class RoundrobinTests(TestCase):
|
||||
"""Tests for ``roundrobin()``"""
|
||||
|
||||
def test_even_groups(self):
|
||||
"""Ensure ordered output from evenly populated iterables"""
|
||||
self.assertEqual(
|
||||
list(mi.roundrobin('ABC', [1, 2, 3], range(3))),
|
||||
['A', 1, 0, 'B', 2, 1, 'C', 3, 2]
|
||||
)
|
||||
|
||||
def test_uneven_groups(self):
|
||||
"""Ensure ordered output from unevenly populated iterables"""
|
||||
self.assertEqual(
|
||||
list(mi.roundrobin('ABCD', [1, 2], range(0))),
|
||||
['A', 1, 'B', 2, 'C', 'D']
|
||||
)
|
||||
|
||||
|
||||
class PartitionTests(TestCase):
|
||||
"""Tests for ``partition()``"""
|
||||
|
||||
def test_bool(self):
|
||||
"""Test when pred() returns a boolean"""
|
||||
lesser, greater = mi.partition(lambda x: x > 5, range(10))
|
||||
self.assertEqual(list(lesser), [0, 1, 2, 3, 4, 5])
|
||||
self.assertEqual(list(greater), [6, 7, 8, 9])
|
||||
|
||||
def test_arbitrary(self):
|
||||
"""Test when pred() returns an integer"""
|
||||
divisibles, remainders = mi.partition(lambda x: x % 3, range(10))
|
||||
self.assertEqual(list(divisibles), [0, 3, 6, 9])
|
||||
self.assertEqual(list(remainders), [1, 2, 4, 5, 7, 8])
|
||||
|
||||
|
||||
class PowersetTests(TestCase):
|
||||
"""Tests for ``powerset()``"""
|
||||
|
||||
def test_combinatorics(self):
|
||||
"""Ensure a proper enumeration"""
|
||||
p = mi.powerset([1, 2, 3])
|
||||
self.assertEqual(
|
||||
list(p),
|
||||
[(), (1,), (2,), (3,), (1, 2), (1, 3), (2, 3), (1, 2, 3)]
|
||||
)
|
||||
|
||||
|
||||
class UniqueEverseenTests(TestCase):
|
||||
"""Tests for ``unique_everseen()``"""
|
||||
|
||||
def test_everseen(self):
|
||||
"""ensure duplicate elements are ignored"""
|
||||
u = mi.unique_everseen('AAAABBBBCCDAABBB')
|
||||
self.assertEqual(
|
||||
['A', 'B', 'C', 'D'],
|
||||
list(u)
|
||||
)
|
||||
|
||||
def test_custom_key(self):
|
||||
"""ensure the custom key comparison works"""
|
||||
u = mi.unique_everseen('aAbACCc', key=str.lower)
|
||||
self.assertEqual(list('abC'), list(u))
|
||||
|
||||
def test_unhashable(self):
|
||||
"""ensure things work for unhashable items"""
|
||||
iterable = ['a', [1, 2, 3], [1, 2, 3], 'a']
|
||||
u = mi.unique_everseen(iterable)
|
||||
self.assertEqual(list(u), ['a', [1, 2, 3]])
|
||||
|
||||
def test_unhashable_key(self):
|
||||
"""ensure things work for unhashable items with a custom key"""
|
||||
iterable = ['a', [1, 2, 3], [1, 2, 3], 'a']
|
||||
u = mi.unique_everseen(iterable, key=lambda x: x)
|
||||
self.assertEqual(list(u), ['a', [1, 2, 3]])
|
||||
|
||||
|
||||
class UniqueJustseenTests(TestCase):
|
||||
"""Tests for ``unique_justseen()``"""
|
||||
|
||||
def test_justseen(self):
|
||||
"""ensure only last item is remembered"""
|
||||
u = mi.unique_justseen('AAAABBBCCDABB')
|
||||
self.assertEqual(list('ABCDAB'), list(u))
|
||||
|
||||
def test_custom_key(self):
|
||||
"""ensure the custom key comparison works"""
|
||||
u = mi.unique_justseen('AABCcAD', str.lower)
|
||||
self.assertEqual(list('ABCAD'), list(u))
|
||||
|
||||
|
||||
class IterExceptTests(TestCase):
|
||||
"""Tests for ``iter_except()``"""
|
||||
|
||||
def test_exact_exception(self):
|
||||
"""ensure the exact specified exception is caught"""
|
||||
l = [1, 2, 3]
|
||||
i = mi.iter_except(l.pop, IndexError)
|
||||
self.assertEqual(list(i), [3, 2, 1])
|
||||
|
||||
def test_generic_exception(self):
|
||||
"""ensure the generic exception can be caught"""
|
||||
l = [1, 2]
|
||||
i = mi.iter_except(l.pop, Exception)
|
||||
self.assertEqual(list(i), [2, 1])
|
||||
|
||||
def test_uncaught_exception_is_raised(self):
|
||||
"""ensure a non-specified exception is raised"""
|
||||
l = [1, 2, 3]
|
||||
i = mi.iter_except(l.pop, KeyError)
|
||||
self.assertRaises(IndexError, lambda: list(i))
|
||||
|
||||
def test_first(self):
|
||||
"""ensure first is run before the function"""
|
||||
l = [1, 2, 3]
|
||||
f = lambda: 25
|
||||
i = mi.iter_except(l.pop, IndexError, f)
|
||||
self.assertEqual(list(i), [25, 3, 2, 1])
|
||||
|
||||
|
||||
class FirstTrueTests(TestCase):
|
||||
"""Tests for ``first_true()``"""
|
||||
|
||||
def test_something_true(self):
|
||||
"""Test with no keywords"""
|
||||
self.assertEqual(mi.first_true(range(10)), 1)
|
||||
|
||||
def test_nothing_true(self):
|
||||
"""Test default return value."""
|
||||
self.assertEqual(mi.first_true([0, 0, 0]), False)
|
||||
|
||||
def test_default(self):
|
||||
"""Test with a default keyword"""
|
||||
self.assertEqual(mi.first_true([0, 0, 0], default='!'), '!')
|
||||
|
||||
def test_pred(self):
|
||||
"""Test with a custom predicate"""
|
||||
self.assertEqual(
|
||||
mi.first_true([2, 4, 6], pred=lambda x: x % 3 == 0), 6
|
||||
)
|
||||
|
||||
|
||||
class RandomProductTests(TestCase):
|
||||
"""Tests for ``random_product()``
|
||||
|
||||
Since random.choice() has different results with the same seed across
|
||||
python versions 2.x and 3.x, these tests use highly probably events to
|
||||
create predictable outcomes across platforms.
|
||||
"""
|
||||
|
||||
def test_simple_lists(self):
|
||||
"""Ensure that one item is chosen from each list in each pair.
|
||||
Also ensure that each item from each list eventually appears in
|
||||
the chosen combinations.
|
||||
|
||||
Odds are roughly 1 in 7.1 * 10e16 that one item from either list will
|
||||
not be chosen after 100 samplings of one item from each list. Just to
|
||||
be safe, better use a known random seed, too.
|
||||
|
||||
"""
|
||||
nums = [1, 2, 3]
|
||||
lets = ['a', 'b', 'c']
|
||||
n, m = zip(*[mi.random_product(nums, lets) for _ in range(100)])
|
||||
n, m = set(n), set(m)
|
||||
self.assertEqual(n, set(nums))
|
||||
self.assertEqual(m, set(lets))
|
||||
self.assertEqual(len(n), len(nums))
|
||||
self.assertEqual(len(m), len(lets))
|
||||
|
||||
def test_list_with_repeat(self):
|
||||
"""ensure multiple items are chosen, and that they appear to be chosen
|
||||
from one list then the next, in proper order.
|
||||
|
||||
"""
|
||||
nums = [1, 2, 3]
|
||||
lets = ['a', 'b', 'c']
|
||||
r = list(mi.random_product(nums, lets, repeat=100))
|
||||
self.assertEqual(2 * 100, len(r))
|
||||
n, m = set(r[::2]), set(r[1::2])
|
||||
self.assertEqual(n, set(nums))
|
||||
self.assertEqual(m, set(lets))
|
||||
self.assertEqual(len(n), len(nums))
|
||||
self.assertEqual(len(m), len(lets))
|
||||
|
||||
|
||||
class RandomPermutationTests(TestCase):
|
||||
"""Tests for ``random_permutation()``"""
|
||||
|
||||
def test_full_permutation(self):
|
||||
"""ensure every item from the iterable is returned in a new ordering
|
||||
|
||||
15 elements have a 1 in 1.3 * 10e12 of appearing in sorted order, so
|
||||
we fix a seed value just to be sure.
|
||||
|
||||
"""
|
||||
i = range(15)
|
||||
r = mi.random_permutation(i)
|
||||
self.assertEqual(set(i), set(r))
|
||||
if i == r:
|
||||
raise AssertionError("Values were not permuted")
|
||||
|
||||
def test_partial_permutation(self):
|
||||
"""ensure all returned items are from the iterable, that the returned
|
||||
permutation is of the desired length, and that all items eventually
|
||||
get returned.
|
||||
|
||||
Sampling 100 permutations of length 5 from a set of 15 leaves a
|
||||
(2/3)^100 chance that an item will not be chosen. Multiplied by 15
|
||||
items, there is a 1 in 2.6e16 chance that at least 1 item will not
|
||||
show up in the resulting output. Using a random seed will fix that.
|
||||
|
||||
"""
|
||||
items = range(15)
|
||||
item_set = set(items)
|
||||
all_items = set()
|
||||
for _ in range(100):
|
||||
permutation = mi.random_permutation(items, 5)
|
||||
self.assertEqual(len(permutation), 5)
|
||||
permutation_set = set(permutation)
|
||||
self.assertLessEqual(permutation_set, item_set)
|
||||
all_items |= permutation_set
|
||||
self.assertEqual(all_items, item_set)
|
||||
|
||||
|
||||
class RandomCombinationTests(TestCase):
|
||||
"""Tests for ``random_combination()``"""
|
||||
|
||||
def test_psuedorandomness(self):
|
||||
"""ensure different subsets of the iterable get returned over many
|
||||
samplings of random combinations"""
|
||||
items = range(15)
|
||||
all_items = set()
|
||||
for _ in range(50):
|
||||
combination = mi.random_combination(items, 5)
|
||||
all_items |= set(combination)
|
||||
self.assertEqual(all_items, set(items))
|
||||
|
||||
def test_no_replacement(self):
|
||||
"""ensure that elements are sampled without replacement"""
|
||||
items = range(15)
|
||||
for _ in range(50):
|
||||
combination = mi.random_combination(items, len(items))
|
||||
self.assertEqual(len(combination), len(set(combination)))
|
||||
self.assertRaises(
|
||||
ValueError, lambda: mi.random_combination(items, len(items) + 1)
|
||||
)
|
||||
|
||||
|
||||
class RandomCombinationWithReplacementTests(TestCase):
|
||||
"""Tests for ``random_combination_with_replacement()``"""
|
||||
|
||||
def test_replacement(self):
|
||||
"""ensure that elements are sampled with replacement"""
|
||||
items = range(5)
|
||||
combo = mi.random_combination_with_replacement(items, len(items) * 2)
|
||||
self.assertEqual(2 * len(items), len(combo))
|
||||
if len(set(combo)) == len(combo):
|
||||
raise AssertionError("Combination contained no duplicates")
|
||||
|
||||
def test_pseudorandomness(self):
|
||||
"""ensure different subsets of the iterable get returned over many
|
||||
samplings of random combinations"""
|
||||
items = range(15)
|
||||
all_items = set()
|
||||
for _ in range(50):
|
||||
combination = mi.random_combination_with_replacement(items, 5)
|
||||
all_items |= set(combination)
|
||||
self.assertEqual(all_items, set(items))
|
||||
|
||||
|
||||
class NthCombinationTests(TestCase):
|
||||
def test_basic(self):
|
||||
iterable = 'abcdefg'
|
||||
r = 4
|
||||
for index, expected in enumerate(combinations(iterable, r)):
|
||||
actual = mi.nth_combination(iterable, r, index)
|
||||
self.assertEqual(actual, expected)
|
||||
|
||||
def test_long(self):
|
||||
actual = mi.nth_combination(range(180), 4, 2000000)
|
||||
expected = (2, 12, 35, 126)
|
||||
self.assertEqual(actual, expected)
|
||||
|
||||
def test_invalid_r(self):
|
||||
for r in (-1, 3):
|
||||
with self.assertRaises(ValueError):
|
||||
mi.nth_combination([], r, 0)
|
||||
|
||||
def test_invalid_index(self):
|
||||
with self.assertRaises(IndexError):
|
||||
mi.nth_combination('abcdefg', 3, -36)
|
||||
|
||||
|
||||
class PrependTests(TestCase):
|
||||
def test_basic(self):
|
||||
value = 'a'
|
||||
iterator = iter('bcdefg')
|
||||
actual = list(mi.prepend(value, iterator))
|
||||
expected = list('abcdefg')
|
||||
self.assertEqual(actual, expected)
|
||||
|
||||
def test_multiple(self):
|
||||
value = 'ab'
|
||||
iterator = iter('cdefg')
|
||||
actual = tuple(mi.prepend(value, iterator))
|
||||
expected = ('ab',) + tuple('cdefg')
|
||||
self.assertEqual(actual, expected)
|
Loading…
Add table
Add a link
Reference in a new issue