diff --git a/RELEASENOTE.md b/RELEASENOTE.md new file mode 100644 index 0000000..1dd5d8c --- /dev/null +++ b/RELEASENOTE.md @@ -0,0 +1,8 @@ + +# 1.1.0 + +Change `Option` to make its instance object same as its `code`. + +# 1.0.0 + +General Available \ No newline at end of file diff --git a/docs/operators.md b/docs/operators.md index 3a80dce..0509824 100644 --- a/docs/operators.md +++ b/docs/operators.md @@ -1,5 +1,8 @@ # Supported Operators of `Option` + Option is refactored. An Option object is now same as its code. + This doc need to be re-written. + `Option.code` is the real value of the enum/option item. Somehow we need to use codes like `if active_state == MyOption.RUNNING ...` to check the status. For convenience using it, some of the operators are override. diff --git a/optenum/__init__.py b/optenum/__init__.py index a2382ad..10409e0 100644 --- a/optenum/__init__.py +++ b/optenum/__init__.py @@ -30,4 +30,4 @@ __copyright__ = "Copyright (c) 2019 Samuel Chen (Chen Wei)" __license__ = "MIT" __summary__ = "A missing python Enum/option lib supports enum code, name, text, even (code, name) tuple list and so on." -__uri__ = "https://github.com/samuelchen/optenum.git" \ No newline at end of file +__uri__ = "https://github.com/samuelchen/optenum.git" diff --git a/optenum/option.py b/optenum/option.py index 4394949..e33c00b 100644 --- a/optenum/option.py +++ b/optenum/option.py @@ -2,338 +2,80 @@ Option class represents a single option in a list of options/enum """ import six -from datetime import datetime, date, time from .mysix import is_identifier +NUMBER_TYPES = six.integer_types + (float,) +DATE_TYPES = () # (datetime, date, time) -class Option(object): - """ A single option with code, name and text""" - - NOT_DEFINED = None - """Not defined option.""" - - NUMBER_TYPES = six.integer_types + (float, ) - DATE_TYPES = () # (datetime, date, time) +AVAILABLE_CODE_TYPES = six.string_types + NUMBER_TYPES + DATE_TYPES +AVAILABLE_CODE_TYPES_STR = ', '.join(t.__name__ for t in AVAILABLE_CODE_TYPES) - AVAILABLE_CODE_TYPES = six.string_types + NUMBER_TYPES + DATE_TYPES - AVAILABLE_CODE_TYPES_STR = ', '.join(t.__name__ for t in AVAILABLE_CODE_TYPES) - def __init__(self, code, name, text=None): - """ - Initialize an option - :param code: The real value of an option or enum entry. - :param name: The readable name of an option or enum entry. It can also be used as dot annotation. - It is alphanumeric or "_" in uppercase and start with alphabet. - :param text: The description of the option/enum entry. It's used to display. Can be i18n. - """ +class OptionPseudo(object): + pass - if not (code is None or isinstance(code, self.AVAILABLE_CODE_TYPES)): - raise TypeError('Option code must be one of %s' % self.AVAILABLE_CODE_TYPES_STR) - if name is not None: - if not isinstance(name, six.string_types): - raise ValueError('Option name must be string type.') - - if not (name[0].isalpha() and is_identifier(name) and name.isupper()): - raise ValueError('Option name must be alphanumeric or "_" in uppercase and start with alphabet.') - - if not ((code is None and name is None and text is None) or (code is not None and name is not None)): - raise ValueError('Option code and name must be not None unless code, name and text are all None.') +class OptionMeta(type): - self.__code = code - self.__name = name - self.__text = text + def __new__(mcs, name, bases, namespace): + cls_instance = super(OptionMeta, mcs).__new__(mcs, name, bases, namespace) - self.__code_type = type(code) + cls_instance.NOT_DEFINED = None + """Not defined option.""" - @property - def code(self): - """The real value of an option or enum entry.""" - return self.__code + cls_instance.NUMBER_TYPES = six.integer_types + (float,) + cls_instance.DATE_TYPES = () # (datetime, date, time) - @property - def name(self): - """ - The readable string of an option or enum entry. It's commonly uppercase. - It can also be used as dot annotation. - """ - return self.__name + cls_instance.AVAILABLE_CODE_TYPES = six.string_types + NUMBER_TYPES + DATE_TYPES + cls_instance.AVAILABLE_CODE_TYPES_STR = ', '.join(t.__name__ for t in AVAILABLE_CODE_TYPES) - @property - def text(self): - """The description of the option/enum entry. It's used to display. Can be i18n.""" - return self.__text + return cls_instance - def get_text(self): - """ - Returns `text` of the option. If `text` is None, returns `name` in lower case as text. - If name is also None, return `None` object converted string. - """ - return str(None) if self.name is None else self.name.lower() if self.text is None else self.text + def __call__(cls, code, name, text=None): - def __repr__(self): - return "<%s code=%s name=%s text=%s>" % (self.__class__.__name__, self.code, self.name, self.text) + if code is None or name is None: + raise ValueError('code or name can not be None') - def __str__(self): - return str(self.code) + if not (code is None or isinstance(code, Option.AVAILABLE_CODE_TYPES)): + raise TypeError('Option code must be one of %s' % AVAILABLE_CODE_TYPES_STR) - # Compare Operators + if name is not None: + if not isinstance(name, six.string_types): + raise ValueError('Option name must be string type.') - def __op_cmp_check__(self, op, other): - """ - Check if self is compatible with `other` on operation `op` - :param op: operation such as "+", "-" and so on - :param other: The other value of the operation to be compared with self. - :return: void. (raise error directly if not compatible) - """ - other_is_option = False - if isinstance(other, Option): - other = other.code - other_is_option = True - type_other = type(other) + if not (name[0].isalpha() and is_identifier(name) and name.isupper()): + raise ValueError('Option name must be alphanumeric or "_" in uppercase and start with alphabet.') - if isinstance(other, self.NUMBER_TYPES + self.DATE_TYPES) and isinstance(self.code, self.NUMBER_TYPES + self.DATE_TYPES): - pass - elif isinstance(other, six.string_types) and isinstance(self.code, six.string_types): - pass - elif other is None or self.code is None or self == Option.NOT_DEFINED: - pass - else: - raise TypeError("'%s' not supported between instances of 'Option(%s)' and '%s'" % ( - op, self.__code_type.__name__, - 'Option(%s)' % type_other.__name__ if other_is_option else type_other.__name__ - )) + if not ((code is None and name is None and text is None) or (code is not None and name is not None)): + raise ValueError('Option code and name must be not None unless code, name and text are all None.') - # if six.PY2: - # def __cmp__(self, other): - # self.__op_cmp_check__('==', other) - # - # if isinstance(other, Option): - # if self.code == other.code and self.name == other.name and self.text == other.text: - # return 0 - # elif self.code < other.code: - # return -1 - # else: - # return 1 - # else: - # return self.code.__cmp__(other) + cls_code = type(code) + cls_option = type('Option(%s)' % cls_code.__name__, (cls_code, OptionPseudo), {}) + obj_option = cls_option(code) + obj_option.code = code + obj_option.name = name + obj_option.text = text - def __eq__(self, other): - self.__op_cmp_check__('==', other) + return obj_option - if isinstance(other, Option): - return self.code == other.code and self.name == other.name and self.text == other.text + def __subclasscheck__(cls, subclass): + if issubclass(subclass, OptionPseudo): + return True else: - return self.code == other - - def __hash__(self): - return super(Option, self).__hash__() - - def __lt__(self, other): - self.__op_cmp_check__('<', other) + return super(OptionMeta, cls).__subclasscheck__(subclass) - return self.code < (other.code if isinstance(other, Option) else other) - - def __le__(self, other): - self.__op_cmp_check__('<=', other) - - return self.code <= (other.code if isinstance(other, Option) else other) - - def __gt__(self, other): - self.__op_cmp_check__('>', other) - - return self.code > (other.code if isinstance(other, Option) else other) - - def __ge__(self, other): - self.__op_cmp_check__('>=', other) - - return self.code >= (other.code if isinstance(other, Option) else other) - - # Math Operators - - def __op_math_check__(self, op, other): - """ - Check if self is compatible with `other` on math operation `op` - :param op: operation such as "+", "-" and so on - :param other: The other value of the operation to be compared with self. - :return: void. (raise error directly if not compatible) - """ - other_is_option = False - if isinstance(other, Option): - other = other.code - other_is_option = True - type_other = type(other) - - if isinstance(other, self.NUMBER_TYPES) and isinstance(self.code, self.NUMBER_TYPES): - pass - elif isinstance(other, six.string_types) and isinstance(self.code, six.string_types): - pass - elif isinstance(other, self.DATE_TYPES) and isinstance(self.code, self.DATE_TYPES): - pass + def __instancecheck__(cls, instance): + if isinstance(instance, OptionPseudo): + return True else: - raise TypeError("'%s' not supported between instances of 'Option(%s)' and '%s'" % ( - op, self.__code_type.__name__, - 'Option(%s)' % type_other.__name__ if other_is_option else type_other.__name__ - )) - - def __op_num_check__(self, op, val): - - val, val_is_option = (val.code, True) if isinstance(val, Option) else (val, False) - type_val = type(val) - type_str = 'Option(%s)' % type_val.__name__ if val_is_option else type_val.__name__ - - if not isinstance(val, self.NUMBER_TYPES): - raise TypeError("'%s' not supported on instances of '%s'" % (op, type_str)) - - def __op_int_check__(self, op, val): - - val, val_is_option = (val.code, True) if isinstance(val, Option) else (val, False) - type_val = type(val) - type_str = 'Option(%s)' % type_val.__name__ if val_is_option else type_val.__name__ - - if not isinstance(val, six.integer_types): - raise TypeError("'%s' not supported on instances of '%s'" % (op, type_str)) - - def __neg__(self): - self.__op_num_check__('-(negative)', self) - - return - self.code - - def __pos__(self): - self.__op_num_check__('+(positive)', self) - - return + self.code - - def __abs__(self): - self.__op_num_check__('abs', self) - - return abs(self.code) - - def __int__(self): - return int(self.code) + return super(OptionMeta, cls).__instancecheck__(instance) - def __float__(self): - return float(self.code) - def __invert__(self): - self.__op_int_check__('~', self) - - return ~ self.code - - def __lshift__(self, other): - self.__op_int_check__('<<', self) - self.__op_int_check__('<<', other) - - return self.code << other - - def __rlshift__(self, other): - self.__op_int_check__('<<', self) - self.__op_int_check__('<<', other) - - return other << self.code - - def __rshift__(self, other): - self.__op_int_check__('>>', self) - self.__op_int_check__('>>', other) - - return self.code >> other - - def __rrshift__(self, other): - self.__op_int_check__('>>', self) - self.__op_int_check__('>>', other) - - return other >> self.code - - def __add__(self, other): - self.__op_math_check__('+', other) - - return self.code + (other.code if isinstance(other, Option) else other) - - def __radd__(self, other): - return self.__add__(other) - - def __sub__(self, other): - self.__op_math_check__('-', other) - - return self.code - (other.code if isinstance(other, Option) else other) - - def __rsub__(self, other): - self.__op_math_check__('-', other) - - return (other.code if isinstance(other, Option) else other) - self.code - - def __mul__(self, other): - self.__op_num_check__('*', other) - - return self.code * (other.code if isinstance(other, Option) else other) - - def __rmul__(self, other): - return self.__mul__(other) - - def __truediv__(self, other): - self.__op_num_check__('/', self) - self.__op_num_check__('/', other) - - return self.code.__truediv__(other.code if isinstance(other, Option) else other) - - def __rtruediv__(self, other): - self.__op_num_check__('/', self) - self.__op_num_check__('/', other) - - return self.code.__rtruediv__(other.code if isinstance(other, Option) else other) - - def __floordiv__(self, other): - self.__op_num_check__('//', self) - self.__op_num_check__('//', other) - - return self.code.__floordiv__(other.code if isinstance(other, Option) else other) - - def __rfloordiv__(self, other): - self.__op_num_check__('//', self) - self.__op_num_check__('//', other) - - return self.code.__rfloordiv__(other.code if isinstance(other, Option) else other) - - # compatible with PY2 without "from __future__ import division" - def __div__(self, other): - self.__op_num_check__('/', self) - self.__op_num_check__('/', other) - - return self.code.__div__(other.code if isinstance(other, Option) else other) - - def __rdiv__(self, other): - self.__op_num_check__('/', self) - self.__op_num_check__('/', other) - - return self.code.__rdiv__(other.code if isinstance(other, Option) else other) - - def __divmod__(self, other): - self.__op_num_check__('%', self) - self.__op_num_check__('%', other) - - return self.code.__divmod__(other.code if isinstance(other, Option) else other) - - def __rdivmod__(self, other): - self.__op_num_check__('%', self) - self.__op_num_check__('%', other) - - return self.code.__rdivmod__(other.code if isinstance(other, Option) else other) - - def __mod__(self, other): - - self.__op_num_check__('%', self) - self.__op_num_check__('%', other) - - return self.code.__mod__(other.code if isinstance(other, Option) else other) - - def __rmod__(self, other): - self.__op_num_check__('%', self) - self.__op_num_check__('%', other) - - return self.code.__rmod__(other.code if isinstance(other, Option) else other) +@six.add_metaclass(OptionMeta) # py2,3 compatibility +class Option(object): + pass -Option.NOT_DEFINED = Option(None, None, None) +__all__ = ('Option', ) -__all__ = ('Option', ) diff --git a/optenum/option.v1.py b/optenum/option.v1.py new file mode 100644 index 0000000..956f5e0 --- /dev/null +++ b/optenum/option.v1.py @@ -0,0 +1,343 @@ +""" +Option class represents a single option in a list of options/enum + +DEPRECATED from v1.1.0 version. +""" +import six +from datetime import datetime, date, time +from .mysix import is_identifier + + +class Option(object): + """ A single option with code, name and text""" + + NOT_DEFINED = None + """Not defined option.""" + + NUMBER_TYPES = six.integer_types + (float, ) + DATE_TYPES = () # (datetime, date, time) + + AVAILABLE_CODE_TYPES = six.string_types + NUMBER_TYPES + DATE_TYPES + AVAILABLE_CODE_TYPES_STR = ', '.join(t.__name__ for t in AVAILABLE_CODE_TYPES) + + def __init__(self, code, name, text=None): + """ + Initialize an option + :param code: The real value of an option or enum entry. + :param name: The readable name of an option or enum entry. It can also be used as dot annotation. + It is alphanumeric or "_" in uppercase and start with alphabet. + :param text: The description of the option/enum entry. It's used to display. Can be i18n. + """ + + if not (code is None or isinstance(code, self.AVAILABLE_CODE_TYPES)): + raise TypeError('Option code must be one of %s' % self.AVAILABLE_CODE_TYPES_STR) + + if name is not None: + if not isinstance(name, six.string_types): + raise ValueError('Option name must be string type.') + + if not (name[0].isalpha() and is_identifier(name) and name.isupper()): + raise ValueError('Option name must be alphanumeric or "_" in uppercase and start with alphabet.') + + if not ((code is None and name is None and text is None) or (code is not None and name is not None)): + raise ValueError('Option code and name must be not None unless code, name and text are all None.') + + self.__code = code + self.__name = name + self.__text = text + + self.__code_type = type(code) + + @property + def code(self): + """The real value of an option or enum entry.""" + return self.__code + + @property + def name(self): + """ + The readable string of an option or enum entry. It's commonly uppercase. + It can also be used as dot annotation. + """ + return self.__name + + @property + def text(self): + """The description of the option/enum entry. It's used to display. Can be i18n.""" + return self.__text + + def get_text(self): + """ + Returns `text` of the option. If `text` is None, returns `name` in lower case as text. + If name is also None, return `None` object converted string. + """ + return str(None) if self.name is None else self.name.lower() if self.text is None else self.text + + def __repr__(self): + return "<%s code=%s name=%s text=%s>" % (self.__class__.__name__, self.code, self.name, self.text) + + def __str__(self): + return str(self.code) + + # Compare Operators + + def __op_cmp_check__(self, op, other): + """ + Check if self is compatible with `other` on operation `op` + :param op: operation such as "+", "-" and so on + :param other: The other value of the operation to be compared with self. + :return: void. (raise error directly if not compatible) + """ + other_is_option = False + if isinstance(other, Option): + other = other.code + other_is_option = True + type_other = type(other) + + if isinstance(other, self.NUMBER_TYPES + self.DATE_TYPES) and isinstance(self.code, self.NUMBER_TYPES + self.DATE_TYPES): + pass + elif isinstance(other, six.string_types) and isinstance(self.code, six.string_types): + pass + elif other is None or self.code is None or self == Option.NOT_DEFINED: + pass + else: + raise TypeError("'%s' not supported between instances of 'Option(%s)' and '%s'" % ( + op, self.__code_type.__name__, + 'Option(%s)' % type_other.__name__ if other_is_option else type_other.__name__ + )) + + # if six.PY2: + # def __cmp__(self, other): + # self.__op_cmp_check__('==', other) + # + # if isinstance(other, Option): + # if self.code == other.code and self.name == other.name and self.text == other.text: + # return 0 + # elif self.code < other.code: + # return -1 + # else: + # return 1 + # else: + # return self.code.__cmp__(other) + + def __eq__(self, other): + self.__op_cmp_check__('==', other) + + if isinstance(other, Option): + return self.code == other.code and self.name == other.name and self.text == other.text + else: + return self.code == other + + def __hash__(self): + """return hash of `code` to ensure key access to `set`, `dict` or other hash based collection""" + # return super(Option, self).__hash__() + return self.code.__hash__() + + def __lt__(self, other): + self.__op_cmp_check__('<', other) + + return self.code < (other.code if isinstance(other, Option) else other) + + def __le__(self, other): + self.__op_cmp_check__('<=', other) + + return self.code <= (other.code if isinstance(other, Option) else other) + + def __gt__(self, other): + self.__op_cmp_check__('>', other) + + return self.code > (other.code if isinstance(other, Option) else other) + + def __ge__(self, other): + self.__op_cmp_check__('>=', other) + + return self.code >= (other.code if isinstance(other, Option) else other) + + # Math Operators + + def __op_math_check__(self, op, other): + """ + Check if self is compatible with `other` on math operation `op` + :param op: operation such as "+", "-" and so on + :param other: The other value of the operation to be compared with self. + :return: void. (raise error directly if not compatible) + """ + other_is_option = False + if isinstance(other, Option): + other = other.code + other_is_option = True + type_other = type(other) + + if isinstance(other, self.NUMBER_TYPES) and isinstance(self.code, self.NUMBER_TYPES): + pass + elif isinstance(other, six.string_types) and isinstance(self.code, six.string_types): + pass + elif isinstance(other, self.DATE_TYPES) and isinstance(self.code, self.DATE_TYPES): + pass + else: + raise TypeError("'%s' not supported between instances of 'Option(%s)' and '%s'" % ( + op, self.__code_type.__name__, + 'Option(%s)' % type_other.__name__ if other_is_option else type_other.__name__ + )) + + def __op_num_check__(self, op, val): + + val, val_is_option = (val.code, True) if isinstance(val, Option) else (val, False) + type_val = type(val) + type_str = 'Option(%s)' % type_val.__name__ if val_is_option else type_val.__name__ + + if not isinstance(val, self.NUMBER_TYPES): + raise TypeError("'%s' not supported on instances of '%s'" % (op, type_str)) + + def __op_int_check__(self, op, val): + + val, val_is_option = (val.code, True) if isinstance(val, Option) else (val, False) + type_val = type(val) + type_str = 'Option(%s)' % type_val.__name__ if val_is_option else type_val.__name__ + + if not isinstance(val, six.integer_types): + raise TypeError("'%s' not supported on instances of '%s'" % (op, type_str)) + + def __neg__(self): + self.__op_num_check__('-(negative)', self) + + return - self.code + + def __pos__(self): + self.__op_num_check__('+(positive)', self) + + return + self.code + + def __abs__(self): + self.__op_num_check__('abs', self) + + return abs(self.code) + + def __int__(self): + return int(self.code) + + def __float__(self): + return float(self.code) + + def __invert__(self): + self.__op_int_check__('~', self) + + return ~ self.code + + def __lshift__(self, other): + self.__op_int_check__('<<', self) + self.__op_int_check__('<<', other) + + return self.code << other + + def __rlshift__(self, other): + self.__op_int_check__('<<', self) + self.__op_int_check__('<<', other) + + return other << self.code + + def __rshift__(self, other): + self.__op_int_check__('>>', self) + self.__op_int_check__('>>', other) + + return self.code >> other + + def __rrshift__(self, other): + self.__op_int_check__('>>', self) + self.__op_int_check__('>>', other) + + return other >> self.code + + def __add__(self, other): + self.__op_math_check__('+', other) + + return self.code + (other.code if isinstance(other, Option) else other) + + def __radd__(self, other): + return self.__add__(other) + + def __sub__(self, other): + self.__op_math_check__('-', other) + + return self.code - (other.code if isinstance(other, Option) else other) + + def __rsub__(self, other): + self.__op_math_check__('-', other) + + return (other.code if isinstance(other, Option) else other) - self.code + + def __mul__(self, other): + self.__op_num_check__('*', other) + + return self.code * (other.code if isinstance(other, Option) else other) + + def __rmul__(self, other): + return self.__mul__(other) + + def __truediv__(self, other): + self.__op_num_check__('/', self) + self.__op_num_check__('/', other) + + return self.code.__truediv__(other.code if isinstance(other, Option) else other) + + def __rtruediv__(self, other): + self.__op_num_check__('/', self) + self.__op_num_check__('/', other) + + return self.code.__rtruediv__(other.code if isinstance(other, Option) else other) + + def __floordiv__(self, other): + self.__op_num_check__('//', self) + self.__op_num_check__('//', other) + + return self.code.__floordiv__(other.code if isinstance(other, Option) else other) + + def __rfloordiv__(self, other): + self.__op_num_check__('//', self) + self.__op_num_check__('//', other) + + return self.code.__rfloordiv__(other.code if isinstance(other, Option) else other) + + # compatible with PY2 without "from __future__ import division" + def __div__(self, other): + self.__op_num_check__('/', self) + self.__op_num_check__('/', other) + + return self.code.__div__(other.code if isinstance(other, Option) else other) + + def __rdiv__(self, other): + self.__op_num_check__('/', self) + self.__op_num_check__('/', other) + + return self.code.__rdiv__(other.code if isinstance(other, Option) else other) + + def __divmod__(self, other): + self.__op_num_check__('%', self) + self.__op_num_check__('%', other) + + return self.code.__divmod__(other.code if isinstance(other, Option) else other) + + def __rdivmod__(self, other): + self.__op_num_check__('%', self) + self.__op_num_check__('%', other) + + return self.code.__rdivmod__(other.code if isinstance(other, Option) else other) + + def __mod__(self, other): + + self.__op_num_check__('%', self) + self.__op_num_check__('%', other) + + return self.code.__mod__(other.code if isinstance(other, Option) else other) + + def __rmod__(self, other): + self.__op_num_check__('%', self) + self.__op_num_check__('%', other) + + return self.code.__rmod__(other.code if isinstance(other, Option) else other) + + +Option.NOT_DEFINED = Option(None, None, None) + + +__all__ = ('Option', ) diff --git a/optenum/options.py b/optenum/options.py index 2e7a7a8..5299403 100644 --- a/optenum/options.py +++ b/optenum/options.py @@ -104,7 +104,7 @@ def __contains__(cls, item): else: return item in cls.__get_code_options_mapping().keys() - def get(cls, key, default=Option.NOT_DEFINED): + def get(cls, key, default=None): return cls.__get_name_options_mapping().get(key, default) @property diff --git a/optenum/version.py b/optenum/version.py index 64c5d82..c456a0e 100644 --- a/optenum/version.py +++ b/optenum/version.py @@ -1,3 +1,3 @@ """ version file """ -__version__ = '1.0.0' \ No newline at end of file +__version__ = '1.1.0' diff --git a/setup.py b/setup.py index 11e944e..1527192 100644 --- a/setup.py +++ b/setup.py @@ -90,7 +90,7 @@ # # This field corresponds to the "Home-Page" metadata field: # https://packaging.python.org/specifications/core-metadata/#home-page-optional - url='https://github.com/samuelchen/optenum', # Optional + url='http://en.samuelchen.net/project/optenum/', # Optional # This should be your name or the name of the organization which owns the # project. diff --git a/tests/test_option.py b/tests/test_option.py index 31515a4..2f750ba 100644 --- a/tests/test_option.py +++ b/tests/test_option.py @@ -29,17 +29,28 @@ class Favorite(object): APPLE = Option(1, 'APPLE', 'Apple') BANANA = Option(2, 'BANANA', 'Banana') +store = { + 1: '10 Apples', # Apple = 1 + 2: '20 Bananas', # Banana = 2 + 'F': '4 Footballs', # Football = 'F' + 'B': '3 Basketballs', # Basketball = 'B' + Ball.PING_PONG: '1 PingPong', # PingPong Option object (hash is 'P') + 'P': 'PPP' +} + class TestOption(unittest.TestCase): - def test_not_defined(self): - self.assertIsInstance(Option.NOT_DEFINED, Option) - self.assertIs(Option.NOT_DEFINED.code, None) + # def test_not_defined(self): + # self.assertIsInstance(Option.NOT_DEFINED, Option) + # self.assertIs(Option.NOT_DEFINED.code, None) def test_option(self): opt = Option(1, 'FOO') self.assertIs(opt.code, 1) self.assertEqual(str(opt), '1') + self.assertIsInstance(opt, Option) + self.assertTrue(issubclass(type(opt), Option)) def test_option_invalid_code_type(self): self.assertRaises(TypeError, Option, *([1, 2, 3], )) @@ -83,8 +94,9 @@ def test_op_eq(self): self.assertEqual(Fruit.APPLE, 1) self.assertEqual(CellPhone.APPLE, 1) self.assertEqual(Fruit.APPLE, Favorite.APPLE) + # self.assertNotEqual(Fruit.BANANA, Favorite.BANANA) self.assertNotEqual(Fruit.BANANA, Favorite.BANANA) - self.assertNotEqual(Fruit.APPLE, CellPhone.APPLE) + self.assertEqual(Fruit.APPLE, CellPhone.APPLE) self.assertEqual(Fruit.APPLE.code, CellPhone.APPLE.code) def test_op_lt(self): @@ -93,8 +105,16 @@ def test_op_lt(self): self.assertLess(1.5, Fruit.ORANGE) self.assertLess(Fruit.APPLE, CellPhone.SAMSUNG) self.assertLess(Ball.BASKETBALL, 'T') - self.assertRaises(TypeError, Ball.FOOTBALL.__lt__, Fruit.BANANA) - self.assertRaises(TypeError, Ball.FOOTBALL.__lt__, 2) + try: + self.assertLess(Ball.FOOTBALL, Fruit.BANANA) + except Exception as e: + self.assertIsInstance(e, (TypeError, AssertionError)) + try: + self.assertLess(Ball.FOOTBALL, 2) + except Exception as e: + self.assertIsInstance(e, (TypeError, AssertionError)) + # self.assertRaises(TypeError, Ball.FOOTBALL.__lt__, Fruit.BANANA) + # self.assertRaises(TypeError, Ball.FOOTBALL.__lt__, 2) def test_op_le(self): self.assertLessEqual(Fruit.APPLE, 2) @@ -102,8 +122,18 @@ def test_op_le(self): self.assertLessEqual(2, Fruit.ORANGE) self.assertLessEqual(Fruit.APPLE, CellPhone.APPLE) self.assertLessEqual(Ball.BASKETBALL, 'B') - self.assertRaises(TypeError, Ball.FOOTBALL.__le__, Fruit.BANANA) - self.assertRaises(TypeError, Ball.FOOTBALL.__le__, 2) + + try: + self.assertLessEqual(Ball.FOOTBALL, Fruit.BANANA) + except Exception as e: + self.assertIsInstance(e, (TypeError, AssertionError)) + try: + self.assertLessEqual(Ball.FOOTBALL, 2) + except Exception as e: + self.assertIsInstance(e, (TypeError, AssertionError)) + + # self.assertRaises(TypeError, Ball.FOOTBALL.__le__, Fruit.BANANA) + # self.assertRaises(TypeError, Ball.FOOTBALL.__le__, 2) def test_op_gt(self): self.assertGreater(Fruit.APPLE, 0) @@ -111,8 +141,18 @@ def test_op_gt(self): self.assertGreater(3.5, Fruit.ORANGE) self.assertGreater(Fruit.BANANA, CellPhone.APPLE) self.assertGreater(Ball.BASKETBALL, '1') - self.assertRaises(TypeError, Ball.FOOTBALL.__gt__, Fruit.BANANA) - self.assertRaises(TypeError, Ball.FOOTBALL.__gt__, 2) + + try: + self.assertGreater(Ball.FOOTBALL, Fruit.BANANA) + except Exception as e: + self.assertIsInstance(e, TypeError) + try: + self.assertGreater(Ball.FOOTBALL, 2) + except Exception as e: + self.assertIsInstance(e, TypeError) + + # self.assertRaises(TypeError, Ball.FOOTBALL.__gt__, Fruit.BANANA) + # self.assertRaises(TypeError, Ball.FOOTBALL.__gt__, 2) def test_op_ge(self): self.assertGreaterEqual(Fruit.APPLE, -1.5) @@ -120,21 +160,40 @@ def test_op_ge(self): self.assertGreaterEqual(2, Fruit.ORANGE) self.assertGreaterEqual(Fruit.BANANA, CellPhone.APPLE) self.assertGreaterEqual(Ball.PING_PONG, 'Jump') - self.assertRaises(TypeError, Ball.FOOTBALL.__ge__, Fruit.BANANA) - self.assertRaises(TypeError, Ball.FOOTBALL.__ge__, 2.1) + + try: + self.assertGreaterEqual(Ball.FOOTBALL, Fruit.BANANA) + except Exception as e: + self.assertIsInstance(e, (TypeError, AssertionError)) + + try: + self.assertGreaterEqual(Ball.FOOTBALL, 2.1) + except Exception as e: + self.assertIsInstance(e, (TypeError, AssertionError)) + + # self.assertRaises(TypeError, Ball.FOOTBALL.__ge__, Fruit.BANANA) + # self.assertRaises(TypeError, Ball.FOOTBALL.__ge__, 2.1) # math op def test_op_neg(self): self.assertEqual(- Fruit.APPLE, -1) self.assertEqual(- Fruit.PEAR, 1) - self.assertRaises(TypeError, Ball.BASKETBALL.__neg__) + try: + -Ball.BASKETBALL + except Exception as e: + self.assertIsInstance(e, TypeError) + # self.assertRaises(TypeError, Ball.BASKETBALL.__neg__) def test_op_pos(self): self.assertEqual(+ Fruit.APPLE, 1) self.assertEqual(+ Fruit.PEAR, -1) self.assertEqual(+ Fruit.APPLE, Fruit.APPLE) - self.assertRaises(TypeError, Ball.BASKETBALL.__pos__) + try: + +Ball.BASKETBALL + except Exception as e: + self.assertIsInstance(e, TypeError) + # self.assertRaises(TypeError, Ball.BASKETBALL.__pos__) def test_op_abs(self): self.assertEqual(abs(Fruit.APPLE), 1) @@ -157,7 +216,10 @@ def test_op_float(self): def test_op_invert(self): self.assertEqual(~ Fruit.APPLE, -2) self.assertEqual(~ Fruit.PEAR, 0) - self.assertRaises(TypeError, Fruit.MONGO.__invert__) + try: + ~ Fruit.MONGO + except Exception as e: + self.assertIsInstance(e, TypeError) self.assertRaises(ValueError, float, Ball.BASKETBALL) def test_op_add(self): @@ -178,8 +240,8 @@ def test_op_mul(self): self.assertEqual(2 * Fruit.APPLE * 1.5, Fruit.BANANA) self.assertEqual(Ball.BASKETBALL * 2, 'BB') self.assertEqual(2 * Ball.BASKETBALL, 'BB') - self.assertRaises(TypeError, Fruit.APPLE.__mul__, *('1',)) - self.assertRaises(TypeError, Ball.BASKETBALL.__mul__, *('1', )) + # self.assertRaises(TypeError, Fruit.APPLE.__mul__, *('1',)) + # self.assertRaises(TypeError, Ball.BASKETBALL.__mul__, *('1', )) def test_op_div(self): if 1 == 3 / 2: @@ -191,16 +253,26 @@ def test_op_div(self): self.assertEqual(Fruit.APPLE / 2, 0) self.assertEqual(Fruit.BANANA / 2, 1) self.assertEqual(3 / Fruit.ORANGE, 1) - self.assertRaises(TypeError, Ball.BASKETBALL.__div__, *(2,)) - self.assertRaises(ZeroDivisionError, Fruit.APPLE.__div__, *(0,)) - self.assertRaises(ZeroDivisionError, Fruit.WATERMELON.__rdiv__, *(3,)) + # self.assertRaises(TypeError, Ball.BASKETBALL.__div__, *(2,)) + # self.assertRaises(ZeroDivisionError, Fruit.APPLE.__div__, *(0,)) + # self.assertRaises(ZeroDivisionError, Fruit.WATERMELON.__rdiv__, *(3,)) else: self.assertEqual(Fruit.APPLE / 2, 0.5) self.assertEqual(Fruit.BANANA / 2, 1.5) self.assertEqual(3 / Fruit.ORANGE, 1.5) - self.assertRaises(TypeError, Ball.BASKETBALL.__truediv__, *(2, )) - self.assertRaises(ZeroDivisionError, Fruit.APPLE.__truediv__, *(0,)) - self.assertRaises(ZeroDivisionError, Fruit.WATERMELON.__rtruediv__, *(3,)) + + # self.assertRaises(TypeError, Ball.BASKETBALL.__truediv__, *(2, )) + # self.assertRaises(ZeroDivisionError, Fruit.APPLE.__truediv__, *(0,)) + # self.assertRaises(ZeroDivisionError, Fruit.WATERMELON.__rtruediv__, *(3,)) + + try: + Ball.BASKETBALL / 2 + except Exception as e: + self.assertIsInstance(e, TypeError) + try: + Fruit.APPLE / 0 + except Exception as e: + self.assertIsInstance(e, ZeroDivisionError) self.assertEqual(2 / Fruit.APPLE, 2) self.assertEqual(Fruit.MONGO / 2, -1.05) @@ -213,9 +285,22 @@ def test_op_div(self): self.assertEqual(Fruit.ORANGE // 2, Fruit.APPLE) self.assertEqual(6 // Fruit.BANANA // 2, Fruit.APPLE) self.assertEqual(2 // Fruit.ORANGE, 1) - self.assertRaises(TypeError, Ball.BASKETBALL.__floordiv__, *(2, )) - self.assertRaises(ZeroDivisionError, Fruit.APPLE.__floordiv__, *(0,)) - self.assertRaises(ZeroDivisionError, Fruit.WATERMELON.__rfloordiv__, *(3,)) + + try: + Ball.BASKETBALL // 2 + except Exception as e: + self.assertIsInstance(e, TypeError) + try: + Fruit.APPLE // 0 + except Exception as e: + self.assertIsInstance(e, ZeroDivisionError) + try: + 3 // Fruit.WATERMELON + except Exception as e: + self.assertIsInstance(e, ZeroDivisionError) + # self.assertRaises(TypeError, Ball.BASKETBALL.__floordiv__, *(2, )) + # self.assertRaises(ZeroDivisionError, Fruit.APPLE.__floordiv__, *(0,)) + # self.assertRaises(ZeroDivisionError, Fruit.WATERMELON.__rfloordiv__, *(3,)) def test_op_mod(self): self.assertEqual(Fruit.MONGO % 2, 1.9) @@ -228,9 +313,44 @@ def test_op_mod(self): def test_op_divmod(self): self.assertEqual(divmod(Fruit.APPLE, 3), (0, 1)) self.assertEqual(divmod(3, Fruit.ORANGE), (1, 1)) - self.assertRaises(TypeError, divmod, *(Fruit.BANANA, 0.5)) - self.assertRaises(ZeroDivisionError, divmod, *(Fruit.APPLE, 0)) - self.assertRaises(ZeroDivisionError, divmod, *(3, Fruit.WATERMELON)) + self.assertEqual(divmod(Fruit.BANANA, 0.5), (6.0, 0.0)) + try: + divmod(Fruit.BANANA, '2') + except Exception as e: + self.assertIsInstance(e, TypeError) + try: + divmod(Fruit.APPLE, 0) + except Exception as e: + self.assertIsInstance(e, ZeroDivisionError) + try: + divmod(3, Fruit.WATERMELON) + except Exception as e: + self.assertIsInstance(e, ZeroDivisionError) + # self.assertRaises(TypeError, divmod, *(Fruit.BANANA, 0.5)) + # self.assertRaises(ZeroDivisionError, divmod, *(Fruit.APPLE, 0)) + # self.assertRaises(ZeroDivisionError, divmod, *(3, Fruit.WATERMELON)) + + def test_hash(self): + self.assertEqual(store.get(Fruit.APPLE), '10 Apples') # Fruit.APPLE hash is 1 + self.assertEqual(store.get(Fruit.BANANA), None) # Fruit.BANANA hash is 3 + self.assertEqual(store.get(Fruit.ORANGE), '20 Bananas') # Fruit.ORANGE hash is 2 + self.assertEqual(store.get(Ball.FOOTBALL), '4 Footballs') # FOOTBALL hash is 'F' + self.assertEqual(store.get(Ball.BASKETBALL), '3 Basketballs') + self.assertEqual(store.get(CellPhone.APPLE), '10 Apples') + + aset = set() + aset.add(Fruit.APPLE) # 1 + aset.add(Fruit.ORANGE) # 2 + aset.add(Ball.FOOTBALL) # 'F' + aset.add(Ball.BASKETBALL) # 'B' + + aset.add(Fruit.APPLE) # 1 + aset.add(CellPhone.APPLE) # 1 + aset.add(CellPhone.HUAWEI) # 2 + aset.add(1) + aset.add(2) + aset.add('B') + self.assertEqual(len(aset), 4) if __name__ == '__main__': diff --git a/tests/test_options_customized.py b/tests/test_options_customized.py index c6e76fd..684e60d 100644 --- a/tests/test_options_customized.py +++ b/tests/test_options_customized.py @@ -109,7 +109,8 @@ def test_get_item(self): self.assertEqual(DoorState.OPEN, DoorState[DoorState.OPEN.name]) self.assertEqual('C', DoorState.get('CLOSED')) self.assertEqual('IC', DoorState.get('IN_CLOSING')) - self.assertEqual(Option.NOT_DEFINED, DoorState.get('Foo')) + # self.assertEqual(Option.NOT_DEFINED, DoorState.get('Foo')) + self.assertEqual(None, DoorState.get('Foo')) self.assertEqual(None, DoorState.get('Foo')) self.assertRaises(KeyError, DoorState.__getitem__, ('FOO',)) self.assertRaises(KeyError, DoorState.__getitem__, (DoorState.OPEN,))