Skip to content

Enh tail #39

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .coveragerc
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
[run]
source=cycler
branch=True
[report]
omit =
*test*
7 changes: 4 additions & 3 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -12,10 +12,11 @@ matrix:

install:
- python setup.py install
- pip install coveralls six
- pip install pytest pytest-cov coverage six

script:
- python run_tests.py
- coverage run run_tests.py
- coverage report -m

after_success:
coveralls
- bash <(curl -s https://codecov.io/bash)
2 changes: 1 addition & 1 deletion appveyor.yml
Original file line number Diff line number Diff line change
@@ -44,7 +44,7 @@ install:
- "python -c \"import struct; print(struct.calcsize('P') * 8)\""

# Install the build and runtime dependencies of the project.
- "%CMD_IN_ENV% pip install -v six nose coveralls"
- "%CMD_IN_ENV% pip install -v six nose pytest pytest-cov coverage"

# Install the generated wheel package to test it
- "python setup.py install"
29 changes: 29 additions & 0 deletions cycler.py
Original file line number Diff line number Diff line change
@@ -558,3 +558,32 @@ def _cycler(label, itr):
itr = (v[lab] for v in itr)

return Cycler._from_iter(label, itr)


def cycler_with_tail(cyl, tail):
'''After the cycle is exhausted continue to yield tail


Parameters
----------
cyl : Cycler
The cycler to iterate through

tail : dict
The dictionary to yield. Must have the same keys as ``cyl``.

Yields
------
sty : dict
'''
tk = set(tail.keys())
if cyl.keys != tk:
raise RuntimeError('The cycler has keys {} and the tail {}. The '
'different keys are {}'.format(cyl.keys,
tk, tk ^ cyl.keys))

for sty in cyl:
yield sty

while True:
yield tail
9 changes: 5 additions & 4 deletions doc/source/index.rst
Original file line number Diff line number Diff line change
@@ -26,12 +26,13 @@ github https://github.com/matplotlib/cycler
cycler
Cycler
concat
cycler_with_tail

The public API of :py:mod:`cycler` consists of a class `Cycler`, a
factory function :func:`cycler`, and a concatenation function
:func:`concat`. The factory function provides a simple interface for
creating 'base' `Cycler` objects while the class takes care of the
composition and iteration logic.
factory function :func:`cycler`, a concatenation function
:func:`concat`, and a few helper functions. The factory function
provides a simple interface for creating 'base' `Cycler` objects while
the class takes care of the composition and iteration logic.


`Cycler` Usage
32 changes: 8 additions & 24 deletions run_tests.py
Original file line number Diff line number Diff line change
@@ -1,27 +1,11 @@
#!/usr/bin/env python
# This file is closely based on tests.py from matplotlib
#
# This allows running the matplotlib tests from the command line: e.g.
#
# $ python run_tests.py -v -d
#
# The arguments are identical to the arguments accepted by nosetests.
#
# See https://nose.readthedocs.org/ for a detailed description of
# these options.
import nose


env = {"NOSE_WITH_COVERAGE": 1,
'NOSE_COVER_PACKAGE': ['cycler'],
'NOSE_COVER_HTML': 1}
plugins = []


def run():

nose.main(addplugins=[x() for x in plugins], env=env)

import sys
import pytest

if __name__ == '__main__':
run()
# show output results from every test function
args = []
args.extend(sys.argv[1:])
# call pytest and exit with the return code from pytest so that
# travis will fail correctly if tests fail
sys.exit(pytest.main(args))
135 changes: 74 additions & 61 deletions test_cycler.py
Original file line number Diff line number Diff line change
@@ -2,26 +2,24 @@

import six
from six.moves import zip, range
from cycler import cycler, Cycler, concat
from nose.tools import (assert_equal, assert_not_equal,
assert_raises, assert_true)
from cycler import cycler, Cycler, concat, cycler_with_tail
import pytest
from itertools import product, cycle, chain
from operator import add, iadd, mul, imul
from collections import defaultdict


def _cycler_helper(c, length, keys, values):
assert_equal(len(c), length)
assert_equal(len(c), len(list(c)))
assert_equal(c.keys, set(keys))

assert len(c) == length
assert len(c) == len(list(c))
assert c.keys == set(keys)
for k, vals in zip(keys, values):
for v, v_target in zip(c, vals):
assert_equal(v[k], v_target)
assert v[k] == v_target


def _cycles_equal(c1, c2):
assert_equal(list(c1), list(c2))
assert list(c1) == list(c2)


def test_creation():
@@ -42,8 +40,10 @@ def test_compose():
yield _cycler_helper, c2+c1, 3, ['c', 'lw'], [list('rgb'), range(3)]
yield _cycles_equal, c2+c1, c1+c2
# miss-matched add lengths
assert_raises(ValueError, add, c1, c3)
assert_raises(ValueError, add, c3, c1)
with pytest.raises(ValueError):
c1 + c3
with pytest.raises(ValueError):
c3 + c1

# multiplication
target = zip(*product(list('rgb'), range(3)))
@@ -92,16 +92,16 @@ def test_constructor():
def test_failures():
c1 = cycler(c='rgb')
c2 = cycler(c=c1)
assert_raises(ValueError, add, c1, c2)
assert_raises(ValueError, iadd, c1, c2)
assert_raises(ValueError, mul, c1, c2)
assert_raises(ValueError, imul, c1, c2)
assert_raises(TypeError, iadd, c2, 'aardvark')
assert_raises(TypeError, imul, c2, 'aardvark')
pytest.raises(ValueError, add, c1, c2)
pytest.raises(ValueError, iadd, c1, c2)
pytest.raises(ValueError, mul, c1, c2)
pytest.raises(ValueError, imul, c1, c2)
pytest.raises(TypeError, iadd, c2, 'aardvark')
pytest.raises(TypeError, imul, c2, 'aardvark')

c3 = cycler(ec=c1)

assert_raises(ValueError, cycler, c=c2+c3)
pytest.raises(ValueError, cycler, c=c2+c3)


def test_simplify():
@@ -123,9 +123,9 @@ def test_multiply():

def test_mul_fails():
c1 = cycler(c='rgb')
assert_raises(TypeError, mul, c1, 2.0)
assert_raises(TypeError, mul, c1, 'a')
assert_raises(TypeError, mul, c1, [])
pytest.raises(TypeError, mul, c1, 2.0)
pytest.raises(TypeError, mul, c1, 'a')
pytest.raises(TypeError, mul, c1, [])


def test_getitem():
@@ -140,15 +140,14 @@ def test_getitem():

def test_fail_getime():
c1 = cycler(lw=range(15))
assert_raises(ValueError, Cycler.__getitem__, c1, 0)
assert_raises(ValueError, Cycler.__getitem__, c1, [0, 1])
pytest.raises(ValueError, Cycler.__getitem__, c1, 0)
pytest.raises(ValueError, Cycler.__getitem__, c1, [0, 1])


def _repr_tester_helper(rpr_func, cyc, target_repr):
test_repr = getattr(cyc, rpr_func)()

assert_equal(six.text_type(test_repr),
six.text_type(target_repr))
assert six.text_type(test_repr) == six.text_type(target_repr)


def test_repr():
@@ -172,13 +171,13 @@ def test_repr():
def test_call():
c = cycler(c='rgb')
c_cycle = c()
assert_true(isinstance(c_cycle, cycle))
assert isinstance(c_cycle, cycle)
j = 0
for a, b in zip(2*c, c_cycle):
j += 1
assert_equal(a, b)
assert a == b

assert_equal(j, len(c) * 2)
assert j == len(c) * 2


def test_copying():
@@ -202,21 +201,21 @@ def test_copying():

c_after = (c1 + c2) * c3

assert_equal(c1, cycler('c', [1, 2, 3]))
assert_equal(c2, cycler('lw', ['r', 'g', 'b']))
assert_equal(c3, cycler('foo', [['y', 'g', 'blue'], ['b', 'k']]))
assert_equal(c_before, (cycler(c=[1, 2, 3], lw=['r', 'g', 'b']) *
cycler('foo', [['y', 'g', 'blue'], ['b', 'k']])))
assert_equal(c_after, (cycler(c=[1, 2, 3], lw=['r', 'g', 'b']) *
cycler('foo', [['y', 'g', 'blue'], ['b', 'k']])))
assert c1 == cycler('c', [1, 2, 3])
assert c2 == cycler('lw', ['r', 'g', 'b'])
assert c3 == cycler('foo', [['y', 'g', 'blue'], ['b', 'k']])
assert c_before == (cycler(c=[1, 2, 3], lw=['r', 'g', 'b']) *
cycler('foo', [['y', 'g', 'blue'], ['b', 'k']]))
assert c_after == (cycler(c=[1, 2, 3], lw=['r', 'g', 'b']) *
cycler('foo', [['y', 'g', 'blue'], ['b', 'k']]))

# Make sure that changing the key for a specific cycler
# doesn't break things for a composed cycler
c = (c1 + c2) * c3
c4 = cycler('bar', c3)
assert_equal(c, (cycler(c=[1, 2, 3], lw=['r', 'g', 'b']) *
cycler('foo', [['y', 'g', 'blue'], ['b', 'k']])))
assert_equal(c3, cycler('foo', [['y', 'g', 'blue'], ['b', 'k']]))
assert c == (cycler(c=[1, 2, 3], lw=['r', 'g', 'b']) *
cycler('foo', [['y', 'g', 'blue'], ['b', 'k']]))
assert c3 == cycler('foo', [['y', 'g', 'blue'], ['b', 'k']])


def test_keychange():
@@ -225,35 +224,35 @@ def test_keychange():
c3 = cycler('ec', 'yk')

c3.change_key('ec', 'edgecolor')
assert_equal(c3, cycler('edgecolor', c3))
assert c3 == cycler('edgecolor', c3)

c = c1 + c2
c.change_key('lw', 'linewidth')
# Changing a key in one cycler should have no
# impact in the original cycler.
assert_equal(c2, cycler('lw', [1, 2, 3]))
assert_equal(c, c1 + cycler('linewidth', c2))
assert c2 == cycler('lw', [1, 2, 3])
assert c == c1 + cycler('linewidth', c2)

c = (c1 + c2) * c3
c.change_key('c', 'color')
assert_equal(c1, cycler('c', 'rgb'))
assert_equal(c, (cycler('color', c1) + c2) * c3)
assert c1 == cycler('c', 'rgb')
assert c == (cycler('color', c1) + c2) * c3

# Perfectly fine, it is a no-op
c.change_key('color', 'color')
assert_equal(c, (cycler('color', c1) + c2) * c3)
assert c == (cycler('color', c1) + c2) * c3

# Can't change a key to one that is already in there
assert_raises(ValueError, Cycler.change_key, c, 'color', 'lw')
pytest.raises(ValueError, Cycler.change_key, c, 'color', 'lw')
# Can't change a key you don't have
assert_raises(KeyError, Cycler.change_key, c, 'c', 'foobar')
pytest.raises(KeyError, Cycler.change_key, c, 'c', 'foobar')


def _eq_test_helper(a, b, res):
if res:
assert_equal(a, b)
assert a == b
else:
assert_not_equal(a, b)
assert a != b


def test_eq():
@@ -273,34 +272,34 @@ def test_eq():


def test_cycler_exceptions():
assert_raises(TypeError, cycler)
assert_raises(TypeError, cycler, 'c', 'rgb', lw=range(3))
assert_raises(TypeError, cycler, 'c')
assert_raises(TypeError, cycler, 'c', 'rgb', 'lw', range(3))
pytest.raises(TypeError, cycler)
pytest.raises(TypeError, cycler, 'c', 'rgb', lw=range(3))
pytest.raises(TypeError, cycler, 'c')
pytest.raises(TypeError, cycler, 'c', 'rgb', 'lw', range(3))


def test_starange_init():
c = cycler('r', 'rgb')
c2 = cycler('lw', range(3))
cy = Cycler(list(c), list(c2), zip)
assert_equal(cy, c + c2)
assert cy == c + c2


def test_concat():
a = cycler('a', range(3))
b = cycler('a', 'abc')
for con, chn in zip(a.concat(b), chain(a, b)):
assert_equal(con, chn)
assert con == chn

for con, chn in zip(concat(a, b), chain(a, b)):
assert_equal(con, chn)
assert con == chn


def test_concat_fail():
a = cycler('a', range(3))
b = cycler('b', range(3))
assert_raises(ValueError, concat, a, b)
assert_raises(ValueError, a.concat, b)
pytest.raises(ValueError, concat, a, b)
pytest.raises(ValueError, a.concat, b)


def _by_key_helper(cy):
@@ -310,23 +309,22 @@ def _by_key_helper(cy):
for k, v in sty.items():
target[k].append(v)

assert_equal(res, target)
assert res == target


def test_by_key_add():
input_dict = dict(c=list('rgb'), lw=[1, 2, 3])
cy = cycler(c=input_dict['c']) + cycler(lw=input_dict['lw'])
res = cy.by_key()
assert_equal(res, input_dict)
assert res == input_dict
yield _by_key_helper, cy


def test_by_key_mul():
input_dict = dict(c=list('rg'), lw=[1, 2, 3])
cy = cycler(c=input_dict['c']) * cycler(lw=input_dict['lw'])
res = cy.by_key()
assert_equal(input_dict['lw'] * len(input_dict['c']),
res['lw'])
assert input_dict['lw'] * len(input_dict['c']) == res['lw']
yield _by_key_helper, cy


@@ -343,3 +341,18 @@ def test_contains():

assert 'a' in ab
assert 'b' in ab


def test_tail():
a = cycler('a', range(3))
tail = {'a': 4}

cy_with_tail = cycler_with_tail(a, tail)
for j in range(3):
next(cy_with_tail)

for j in range(5):
assert {'a': 4} == next(cy_with_tail)

with pytest.raises(RuntimeError):
next(cycler_with_tail(a, {'b': 1}))