From 0dfeeb19331c42364e451cbd877ac8394f8e3a12 Mon Sep 17 00:00:00 2001 From: Samuel Chen Date: Tue, 4 Jun 2019 17:19:41 +0800 Subject: [PATCH] Group supports +(plus) operator --- RELEASENOTE.md | 4 ++ optenum/options.py | 32 ++++++++++++--- optenum/version.py | 2 +- tests/test_option.py | 52 +++++++++++++++--------- tests/test_options_customized.py | 70 ++++++++++++++++++++++++++++++++ 5 files changed, 134 insertions(+), 26 deletions(-) diff --git a/RELEASENOTE.md b/RELEASENOTE.md index 8a1f432..7a7f215 100644 --- a/RELEASENOTE.md +++ b/RELEASENOTE.md @@ -1,4 +1,8 @@ +# v1.1.6 + +* Group supports + operator + # v1.1.5 * Fix issue of creating single option group as class attribute diff --git a/optenum/options.py b/optenum/options.py index 9440c10..0cd7bc2 100644 --- a/optenum/options.py +++ b/optenum/options.py @@ -13,6 +13,7 @@ class OptionGroup(list): def __init__(self, *args): + # to ensure args are in list, consider the following # # class Foo(Options): @@ -23,18 +24,37 @@ def __init__(self, *args): # G2 = OptionGroup(B) # should get args [ (2, 'B is 2') ] # - n = len(args) - if n == 0: - super(OptionGroup, self).__init__() - else: - super(OptionGroup, self).__init__(args) + super(OptionGroup, self).__init__() + for arg in args: + if isinstance(arg, OptionGroup): + for a in arg: + if a not in self: + self.append(a) + else: + if arg not in self: + self.append(arg) def add(self, opt): - super(OptionGroup, self).append(opt) + if isinstance(opt, Option): + super(OptionGroup, self).append(opt) + else: + raise TypeError('Only "%s" can be added into "%s". "%s" is "%s".' + % (Option.__name__, OptionGroup.__name__, opt, type(opt))) def remove(self, opt): super(OptionGroup, self).remove(opt) + def __add__(self, other): + if isinstance(other, OptionGroup): + r = OptionGroup(*super(OptionGroup, self).__add__(other)) + return r + elif isinstance(other, Option): + r = OptionGroup(*super(OptionGroup, self).__add__([other])) + return r + else: + raise TypeError('Can not add "%s"<%s> to %s. Please explicitly convert it to "%s" or "%s"' + % (other, type(other).__name__, OptionGroup.__name__, Option.__name__, OptionGroup.__name__)) + class OptionsMeta(type): diff --git a/optenum/version.py b/optenum/version.py index 9da63a8..11a22a1 100644 --- a/optenum/version.py +++ b/optenum/version.py @@ -1,3 +1,3 @@ """ version file """ -__version__ = '1.1.5' +__version__ = '1.1.6' diff --git a/tests/test_option.py b/tests/test_option.py index 1c55e45..9f3e9c5 100644 --- a/tests/test_option.py +++ b/tests/test_option.py @@ -42,6 +42,9 @@ class Favorite(object): class TestOption(unittest.TestCase): + def raiseAssert(self, e): + raise AssertionError('Should raise %s' % e) + # def test_not_defined(self): # self.assertIsInstance(Option.NOT_DEFINED, Option) # self.assertIs(Option.NOT_DEFINED.code, None) @@ -53,25 +56,12 @@ def test_option(self): self.assertIsInstance(opt, Option) self.assertTrue(issubclass(type(opt), Option)) - # def test_option_tags(self): - # opt = Option(1, 'FOO', tags=['foo', 'Bar_1', 'ba1z', 'QUX']) - # self.assertIn('foo', opt.tags) - # self.assertIn('Bar_1', opt.tags) - # self.assertIn('ba1z', opt.tags) - # self.assertIn('QUX', opt.tags) - # - # def _test_tag_name(tag): - # return Option(1, 'FOO', tags=[tag, ]) - # - # self.assertRaises(ValueError, _test_tag_name, '1a') - # self.assertRaises(ValueError, _test_tag_name, 'a-b') - # self.assertRaises(ValueError, _test_tag_name, ' a') - # self.assertRaises(ValueError, _test_tag_name, 'a b') - # self.assertRaises(ValueError, _test_tag_name, 'ab ') - # self.assertRaises(ValueError, _test_tag_name, 'a.b') - # self.assertRaises(ValueError, _test_tag_name, '') - # self.assertRaises(ValueError, _test_tag_name, '_foo') - # self.assertRaises(ValueError, _test_tag_name, '_BAR') + def test_option_tags(self): + opt = Option(1, 'FOO', tags=['FOO', 'BAR_1', 'BA1Z', 'QUX']) + self.assertIn('FOO', opt.tags) + self.assertIn('BAR_1', opt.tags) + self.assertIn('BA1Z', opt.tags) + self.assertIn('QUX', opt.tags) def test_get_text(self): opt = Option(code=1, name='OPT') @@ -136,10 +126,12 @@ def test_op_lt(self): self.assertLess(Ball.BASKETBALL, 'T') try: self.assertLess(Ball.FOOTBALL, Fruit.BANANA) + self.raiseAssert((TypeError, AssertionError)) except Exception as e: self.assertIsInstance(e, (TypeError, AssertionError)) try: self.assertLess(Ball.FOOTBALL, 2) + self.raiseAssert((TypeError, AssertionError)) except Exception as e: self.assertIsInstance(e, (TypeError, AssertionError)) # self.assertRaises(TypeError, Ball.FOOTBALL.__lt__, Fruit.BANANA) @@ -154,10 +146,12 @@ def test_op_le(self): try: self.assertLessEqual(Ball.FOOTBALL, Fruit.BANANA) + self.raiseAssert((TypeError, AssertionError)) except Exception as e: self.assertIsInstance(e, (TypeError, AssertionError)) try: self.assertLessEqual(Ball.FOOTBALL, 2) + self.raiseAssert((TypeError, AssertionError)) except Exception as e: self.assertIsInstance(e, (TypeError, AssertionError)) @@ -173,10 +167,17 @@ def test_op_gt(self): try: self.assertGreater(Ball.FOOTBALL, Fruit.BANANA) + if six.PY3: + # Only for PY3. Because comparision between str and int is available in Python 2.7 + self.raiseAssert(TypeError) except Exception as e: self.assertIsInstance(e, TypeError) + try: self.assertGreater(Ball.FOOTBALL, 2) + if six.PY3: + # Only for PY3. Because comparision between str and int is available in Python 2.7 + self.raiseAssert(TypeError) except Exception as e: self.assertIsInstance(e, TypeError) @@ -192,11 +193,13 @@ def test_op_ge(self): try: self.assertGreaterEqual(Ball.FOOTBALL, Fruit.BANANA) + self.raiseAssert((TypeError, AssertionError)) except Exception as e: self.assertIsInstance(e, (TypeError, AssertionError)) try: self.assertGreaterEqual(Ball.FOOTBALL, 2.1) + self.raiseAssert((TypeError, AssertionError)) except Exception as e: self.assertIsInstance(e, (TypeError, AssertionError)) @@ -210,6 +213,7 @@ def test_op_neg(self): self.assertEqual(- Fruit.PEAR, 1) try: -Ball.BASKETBALL + self.assertIsInstance(e, TypeError) except Exception as e: self.assertIsInstance(e, TypeError) # self.assertRaises(TypeError, Ball.BASKETBALL.__neg__) @@ -220,6 +224,7 @@ def test_op_pos(self): self.assertEqual(+ Fruit.APPLE, Fruit.APPLE) try: +Ball.BASKETBALL + self.assertIsInstance(e, TypeError) except Exception as e: self.assertIsInstance(e, TypeError) # self.assertRaises(TypeError, Ball.BASKETBALL.__pos__) @@ -247,6 +252,7 @@ def test_op_invert(self): self.assertEqual(~ Fruit.PEAR, 0) try: ~ Fruit.MONGO + self.assertIsInstance(e, TypeError) except Exception as e: self.assertIsInstance(e, TypeError) self.assertRaises(ValueError, float, Ball.BASKETBALL) @@ -296,10 +302,12 @@ def test_op_div(self): try: Ball.BASKETBALL / 2 + self.assertIsInstance(e, TypeError) except Exception as e: self.assertIsInstance(e, TypeError) try: Fruit.APPLE / 0 + self.assertIsInstance(e, ZeroDivisionError) except Exception as e: self.assertIsInstance(e, ZeroDivisionError) @@ -317,14 +325,17 @@ def test_op_div(self): try: Ball.BASKETBALL // 2 + self.assertIsInstance(e, TypeError) except Exception as e: self.assertIsInstance(e, TypeError) try: Fruit.APPLE // 0 + self.assertIsInstance(e, ZeroDivisionError) except Exception as e: self.assertIsInstance(e, ZeroDivisionError) try: 3 // Fruit.WATERMELON + self.assertIsInstance(e, ZeroDivisionError) except Exception as e: self.assertIsInstance(e, ZeroDivisionError) # self.assertRaises(TypeError, Ball.BASKETBALL.__floordiv__, *(2, )) @@ -345,14 +356,17 @@ def test_op_divmod(self): self.assertEqual(divmod(Fruit.BANANA, 0.5), (6.0, 0.0)) try: divmod(Fruit.BANANA, '2') + self.assertIsInstance(e, TypeError) except Exception as e: self.assertIsInstance(e, TypeError) try: divmod(Fruit.APPLE, 0) + self.assertIsInstance(e, ZeroDivisionError) except Exception as e: self.assertIsInstance(e, ZeroDivisionError) try: divmod(3, Fruit.WATERMELON) + self.assertIsInstance(e, ZeroDivisionError) except Exception as e: self.assertIsInstance(e, ZeroDivisionError) # self.assertRaises(TypeError, divmod, *(Fruit.BANANA, 0.5)) diff --git a/tests/test_options_customized.py b/tests/test_options_customized.py index b3f590d..088aff7 100644 --- a/tests/test_options_customized.py +++ b/tests/test_options_customized.py @@ -59,6 +59,9 @@ def baz(self): class TestCustomizedOptions(unittest.TestCase): + def raiseAssert(self, e): + raise AssertionError('Should raise %s' % e) + def test_enum(self): self.assertEqual(Fruit.APPLE, 1) self.assertEqual(Fruit.APPLE.name, 'APPLE') @@ -77,6 +80,8 @@ class MyOptions(Options): foo = 1 Bar = 2 BAZ_1 = 3 + + self.raiseAssert(AttributeError) except Exception as e: self.assertIsInstance(e, AttributeError) @@ -85,6 +90,7 @@ def test_diff_attr_option_name(self): class MyOptions(Options): FOO = 1 BAR = Option(code=2, name='Bar') # name must be 'BAR' + self.raiseAssert(ValueError) except Exception as e: self.assertIsInstance(e, ValueError) @@ -93,6 +99,8 @@ def test_duplicated_options(self): class MyOptions(Options): FOO = 1 FOO = Option(code=2, name='Bar') # duplicated name + + self.raiseAssert(ValueError) except Exception as e: self.assertIsInstance(e, ValueError) @@ -100,6 +108,7 @@ class MyOptions(Options): class MyOptions(Options): FOO = 1 BAR = Option(code=1, name='Bar') # duplicated code + self.raiseAssert(ValueError) except Exception as e: self.assertIsInstance(e, ValueError) @@ -201,6 +210,7 @@ def test_ignore_invalid_name(self): try: class Bar(Options): Invalid= (1, 2) + self.raiseAssert(AttributeError) except Exception as e: self.assertIsInstance(e, AttributeError) @@ -269,6 +279,66 @@ class Foo(Options): self.assertEqual(Foo.G5, (Foo.E, )) self.assertEqual(Foo.G6, (Foo.F, )) + def test_group_plus(self): + + class Foo(Options): + A = 1 + B = 2, 'B is 2' + C = 'C', 'C is letter', ['FOO'] + D = Option('d', name='D') + E = Option('e1', 'E', 'Earth') + F = Option('15', 'F', 'Finish', ['FOO']) + + G1 = G(A, B) + G2 = G(B, C) + G3 = G(C, D) + G4 = G(D, E, F) + + GA = G1 + G2 + GB = G1 + G3 + GC = G2 + G4 + # GD = G1 + G2 + A + # GD = G(G(G1 + G2), G(G3 + B)) + GE = G2 + G3 + G(E) + GF = G(GA + G3 + F) + + self.assertEqual(Foo.GA, (Foo.A, Foo.B, Foo.C)) + self.assertEqual(Foo.GB, (Foo.A, Foo.B, Foo.C, Foo.D)) + self.assertEqual(Foo.GC, (Foo.B, Foo.C, Foo.D, Foo.E, Foo.F)) + self.assertEqual(Foo.GE, (Foo.B, Foo.C, Foo.D, Foo.E)) + self.assertEqual(Foo.GF, (Foo.A, Foo.B, Foo.C, Foo.D, Foo.F)) + + try: + class Bar(Options): + A = 1 + B = 2, 'B is 2' + C = 'C', 'C is letter', ['FOO'] + + G1 = G(A, B) + G2 = G(B, C) + + GD = G1 + G2 + A + + self.raiseAssert(TypeError) + except Exception as e: + self.assertIsInstance(e, TypeError) + + try: + class Baz(Options): + A = 1 + B = 2, 'B is 2' + C = 'C', 'C is letter', ['FOO'] + + G1 = G(A, B) + G2 = G(B, C) + G3 = G(C) + + GD = G(G(G1 + G2), G(G3 + B)) + + self.raiseAssert(TypeError) + except Exception as e: + self.assertIsInstance(e, TypeError) + if __name__ == '__main__': unittest.main()