From 0dfb09527b6b8a8651d9cf10ff4b867001add42e Mon Sep 17 00:00:00 2001 From: Abhas Bhattacharya Date: Fri, 19 Jun 2015 16:19:47 +0530 Subject: [PATCH] Added .save_as, .as_obj, doctest --- .../test_wrap_obj.cpython-27-PYTEST.pyc | Bin 0 -> 55702 bytes fmatch_idea.txt | 24 +- readme | 5 + test_wrap_obj.py | 49 +- wrap_obj.py | 435 +++++++++++++----- wrap_obj.pyc | Bin 0 -> 13610 bytes 6 files changed, 378 insertions(+), 135 deletions(-) create mode 100644 __pycache__/test_wrap_obj.cpython-27-PYTEST.pyc create mode 100644 readme create mode 100644 wrap_obj.pyc diff --git a/__pycache__/test_wrap_obj.cpython-27-PYTEST.pyc b/__pycache__/test_wrap_obj.cpython-27-PYTEST.pyc new file mode 100644 index 0000000000000000000000000000000000000000..8989d0d072bd80db1456f17613f9cd9b95869d2e GIT binary patch literal 55702 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 literal 0 HcmV?d00001 diff --git a/fmatch_idea.txt b/fmatch_idea.txt index fa6f77f..3155633 100644 --- a/fmatch_idea.txt +++ b/fmatch_idea.txt @@ -95,11 +95,29 @@ IDEAS TO DO add more debug info debug info or raise custom exception -allow matches to store in var better __repr__ print +allow matches to store in var (done) + add __mult__ alternative to .times (done) -what about dict and object? (dicts ok, ) +what about dict ? (dicts ok, ) -maybe dict to obj? +maybe dict to obj? (done) + +save_as in multimatch with list behaviour(done) + +Any and OR save_as (done) + +test if data == Multi branch, raise error doesnt break anything +more docs + +test integration save_as and times + +do smart .times for single value + +allow Any with a module.Class(Any), which allows only object - Any + +more doctest for other methods and helpers + +test helpers diff --git a/readme b/readme new file mode 100644 index 0000000..055b8e8 --- /dev/null +++ b/readme @@ -0,0 +1,5 @@ +Run doctest - +python -m doctest .\wrap_obj.py + +Run py.test- +py.test \ No newline at end of file diff --git a/test_wrap_obj.py b/test_wrap_obj.py index 6b63659..ff9185c 100644 --- a/test_wrap_obj.py +++ b/test_wrap_obj.py @@ -1,4 +1,4 @@ -from wrap_obj import WrapObj as w, Any, OR +from wrap_obj import WrapObj, Any, OR, w import collections import pytest @@ -9,7 +9,7 @@ def test_w_single_value(): assert(w("hello") == "hello") assert(w(float("inf")) == float("inf")) assert(w(10.23) == 10.23) - assert(w("abhas") == u"abhas") + # assert(w("abhas") == u"abhas") assert(w(object()) != object()) assert(w(5) != "asd") @@ -29,9 +29,9 @@ def test_w_single_type(): assert(w(float) == 5.0) assert(w(float) == float("inf")) - assert(w(int) == int) - assert(w(float) == float) - assert(w(TypeError) == TypeError) + assert(w(int) != int) + assert(w(float) != float) + assert(w(TypeError) != TypeError) assert(w(int) != 2.33) assert(w(int) != "abhas") @@ -178,3 +178,42 @@ def test_as_obj(): test_obj.b = "qwerty" assert(w({"a": int, "b": Any}).as_obj() == test_obj) + + +def test_w_wrapObj(): + assert(w is WrapObj) + + +def test_save_as(): + dict_match = (w(int).save_as("a") == 5) + assert(dict_match["a"] == 5) + + dict_match = (w([int, w(float).save_as("a")]) == [5, 10.23]) + assert(dict_match["a"] == 10.23) + + dict_match = (w([w(int), w(float).save_as("a")]) == [5, 10.23]) + assert(dict_match["a"] == 10.23) + + dict_match = (w([{"what": w(int).save_as("b")}, w(float).save_as("a")]) == [{"what": 5}, 10.23]) + assert(dict_match["b"] == 5 and dict_match["a"] == 10.23) + with pytest.raises(AssertionError): + assert(dict_match["b"] == 15) + with pytest.raises(AssertionError): + assert(dict_match["a"] == 5) + with pytest.raises(AssertionError): + assert(dict_match["a"] == 10) + + dict_match = (w([{"what": w(int).save_as("a")}, w(float).save_as("a")]) == [{"what": 5}, 10.23]) + assert(dict_match["a"] == 10.23) + + dict_match = (w([[int, w(int).save_as("b")], float]) == [[5, 99], 10.23]) + assert(dict_match["b"] == 99) + + dict_match = (w([w(int).save_as("a")]).times(2) == [2, 3]) + assert(dict_match["a"] != 3) + assert(dict_match["a"] == [2, 3]) + + dict_match_1 = (w([w([int]).times(2).save_as("b")]) == [5, 12]) + dict_match_2 = (w([w([int]).save_as("b").times(2)]) == [5, 12]) + assert(dict_match_1["b"] == [5, 12]) + assert(dict_match_1 == dict_match_2) diff --git a/wrap_obj.py b/wrap_obj.py index 7dea661..a593f2a 100644 --- a/wrap_obj.py +++ b/wrap_obj.py @@ -3,8 +3,11 @@ from enum import Enum from collections import Mapping, Iterable from warnings import warn import logging +import types -logging.debug = print +# logging.debug = print + +__all__ = ["Any", "OR", "WrapObj", "w", "WrapMultiObj"] class MetaAny(type): @@ -15,12 +18,38 @@ class MetaAny(type): def times(self, range_low, range_high=None): return WrapObj([self]).times(range_low, range_high) + def save_as(self, save_key): + return WrapObj(self).save_as(save_key) + class Any(object): + + ''' + Checker class which allows everything. + Usually, you would use it as a catch-all or placeholder. + >>> w(Any) == "asd" + True + >>> w(Any) == 2 + True + >>> w(Any) == 2.35 + True + >>> w(Any) == (lambda x:None) + True + ''' __metaclass__ = MetaAny def makeMultiInstanceMatcher(*list_type): + ''' + Create checker class which allows some types or values. + For combining two or more possible types of a variable + + >>> w(OR(int,float)) == 2.25 and w(OR(int,float)) == 2 + True + >>> w(OR([int,float])) == 2.25 and w(OR([int,float])) == 2 + True + ''' + if len(list_type) == 1 and isinstance(list_type[0], Iterable): list_type = tuple(list_type[0]) @@ -45,6 +74,9 @@ def makeMultiInstanceMatcher(*list_type): def times(self, range_low, range_high=None): return WrapObj([self]).times(range_low, range_high) + def save_as(self, save_key): + return WrapObj(self).save_as(save_key) + class MultiInstanceMatcher(type): __metaclass__ = MetaMultiInstanceMatcher @@ -52,20 +84,44 @@ def makeMultiInstanceMatcher(*list_type): OR = makeMultiInstanceMatcher - CHECK_DIR = Enum("Iterable check direction", ["FORWARDS", "BACKWARDS"]) class WrapObj(object): + ''' + Create WrapObj instance. + data -> Reference Object to match with. + DO_TYPECHECK -> Whether the refernece and checked object must + have same type? Defaults to false. + + >>> WrapObj(int) # doctest: +ELLIPSIS + + >>> WrapObj(float, True) # doctest: +ELLIPSIS + + + ''' + def __init__(self, data, DO_TYPECHECK=False): + super(WrapObj, self).__init__() self.data = data self.DO_TYPECHECK = DO_TYPECHECK self.treat_as_object = False + self.dict_saved_values = {} + self.save_key = None def as_obj(self): + ''' + Matches wrapped dict to object + + >>> class object2(object): pass + >>> test_obj = object2(); test_obj.a = 20 + >>> w({"a": int}).as_obj() == test_obj + True + ''' + try: assert(isinstance(self.data, Mapping)) except AssertionError: @@ -75,116 +131,214 @@ class WrapObj(object): self.treat_as_object = True return self + def save_as(self, arg_name): + ''' + Saves matched value as arg_name, which is + used as key in the returned dict from __eq__ + + >>> _ = w([int,w(float).save_as("a")]) == [2,2.5] + >>> _["a"] == 2.5 + True + ''' + self.save_key = arg_name + return self + def times(self, range_low, range_high=None): - return WrapMultiObj(self.data, range_low, range_high) + ''' + Makes all the items in wrapped iteratble (in given order) match + at least range_low times and at max (range_high-1) in __eq__ + + >>> w([int]).times(2,4) == [2,56,7] + True + >>> w([int]).times(2) == [2,56] + True + + Wrapped object must be a iterator. If only range_low is passed, + must match for exactly range_low times. + + Returns a WrapMultiObj. + ''' + return_obj = WrapMultiObj(self.data, range_low, range_high) + return_obj.save_key = self.save_key + return return_obj def __mul__(self, range_tuple): + ''' + Same as WrapObj.times + + >>> w([int])*(2,4) == [2,56,7] + True + >>> w([int])*(2) == [2,56] + True + ''' return self.times(range_tuple, None) - def __pos__(self): - if (isinstance(self.data, Iterable) and (not isinstance(self.data, Mapping))): - return WrapMultiObj(self.data, [1, 2]) - else: - TypeError("+WrapObj only works when WrapObj.data is list-ish") + # def __pos__(self): + # if (isinstance(self.data, Iterable) and (not isinstance(self.data, Mapping))): + # return WrapMultiObj(self.data, [1, 2]) + # else: + # TypeError("+WrapObj only works when WrapObj.data is list-ish") def __ne__(self, other): + ''' + Handles != matching. Returns opposite of == value + + >>> w(int) != [2,56] + True + ''' return not(self.__eq__(other)) + def match_list(self, other): + ''' + Internal method. Handles self == other, when other is list + ''' + match_dict_or_True = None + check_dir = CHECK_DIR.FORWARDS + multiobj_index_forwards = None + multiobj_index_backwards = None + + self.dict_saved_values = {} + + if (check_dir == CHECK_DIR.FORWARDS): + + for ele_index in range(0, len(self.data), 1): + ele_data = self.data[ele_index] + # as long as not multiobj, check in forwards + if isinstance(ele_data, WrapMultiObj): + multiobj_index_forwards = ele_index + check_dir = CHECK_DIR.BACKWARDS + break + else: + try: + ele_other = other[ele_index] + except IndexError: + # To_be_checked list has length less than matching list + return False + finally: + match_dict_or_True = (WrapObj(ele_data) == ele_other) + if match_dict_or_True and isinstance(match_dict_or_True, dict): + self.dict_saved_values.update(match_dict_or_True) + if not match_dict_or_True: + return False + + if (check_dir == CHECK_DIR.BACKWARDS): + for ele_index in range(0 - 1, -(len(self.data) + 1), -1): + # ele_index is of negative value from -1 to -len (including) + ele_data = self.data[ele_index] + # as long as not multiobj, check in backwards + if isinstance(ele_data, WrapMultiObj): + multiobj_index_backwards = ele_index + check_dir = CHECK_DIR.FORWARDS + break + else: + try: + ele_other = other[ele_index] + except IndexError: + # To_be_checked list has length less than matching list + return False + finally: + match_dict_or_True = (WrapObj(ele_data) == ele_other) + if match_dict_or_True and isinstance(match_dict_or_True, dict): + self.dict_saved_values.update(match_dict_or_True) + if not match_dict_or_True: + return False + + if (match_dict_or_True and (multiobj_index_forwards is None) and (multiobj_index_forwards == multiobj_index_backwards)): + # all of data is in other + # so, if they have same length, everything is perfect + if len(self.data) == len(other): + return (self.dict_saved_values or True) + elif (multiobj_index_forwards + (-multiobj_index_backwards)) != len(self.data): + # there is more than one length_ANY object + raise TypeError("There must be only one object of arbitary range") + else: + # there is only one length ANY object + + # prepare back index for slicing operation + # add 1 (as this one will give outer bound) and if results to 0, set to None (as 0 doesnt mean last index) + multiobj_index_backwards += 1 + multiobj_index_backwards = multiobj_index_backwards or None + ele_wrapmultiobj = self.data[multiobj_index_forwards] + logging.debug("Comparing WrapMultiObj with part of list") + match_dict_or_True = (ele_wrapmultiobj == other[multiobj_index_forwards:multiobj_index_backwards]) + if match_dict_or_True: + if isinstance(match_dict_or_True, dict): + self.dict_saved_values.update(match_dict_or_True) + return (self.dict_saved_values or True) + else: + return False + + def match_dict_or_obj(self, other, dict_or_obj=dict): + ''' + Internal method. Handles self == other, when other is dict or obj + ''' + assert(dict_or_obj in (dict, object)) + match_dict_or_True = None # as in not known yet + + self.dict_saved_values = {} + + for (data_key, data_value) in self.data.iteritems(): + if isinstance(data_key, type): + # todo: stop printing this line, which is broken into parts + warn(("{data_key} is a class used as a key, but it " + "wont match keys which are instances of this type" + "").format(data_key=data_key)) + try: + if dict_or_obj is dict: + # if dict, try using that key + other_value = other[data_key] + elif dict_or_obj is object: + # if obj, try using that attribute + other_value = other.__getattribute__(data_key) + else: + raise TypeError("dict_or_obj must be dict or obj") + except (KeyError if dict_or_obj == dict else AttributeError): + # catch correct kind of error + # so if fails, it doesnt have that key or attribute. so, they dont match + match_dict_or_True = False + return False + else: + match_dict_or_True = (WrapObj(data_value) == other_value) + + if match_dict_or_True and isinstance(match_dict_or_True, dict): + self.dict_saved_values.update(match_dict_or_True) + + if match_dict_or_True is False: + return match_dict_or_True + else: + # should be True + assert(bool(match_dict_or_True) is True) + return (self.dict_saved_values or True) + def __eq__(self, other): + ''' + Handles self == other correctly + + >>> w(float) == 2.23 + True + >>> w(str) == "2.23" + True + ''' if self.DO_TYPECHECK and (type(self.data) != type(other)): return False - elif (self.data == other) or (check_as_value_and_type(other, self.data)): - return True + + elif isinstance(self.data, WrapObj): + return self.data == other + elif isinstance(self.data, WrapMultiObj): return (self.data == [other]) + + elif isinstance(self.data, types.StringTypes): + # take care of strings in normal way unlike lists + return self.data == other + elif (isinstance(self.data, Iterable) and (not isinstance(self.data, Mapping))) and\ (isinstance(other, Iterable) and (not isinstance(other, Mapping))): - does_match = None - check_dir = CHECK_DIR.FORWARDS - multiobj_index_forwards = None - multiobj_index_backwards = None + return self.match_list(other) - if (check_dir == CHECK_DIR.FORWARDS): - - for ele_index in range(0, len(self.data), 1): - ele_data = self.data[ele_index] - # as long as not multiobj, check in forwards - if isinstance(ele_data, WrapMultiObj): - multiobj_index_forwards = ele_index - check_dir = CHECK_DIR.BACKWARDS - break - else: - try: - ele_other = other[ele_index] - except IndexError: - # To_be_checked list has length less than matching list - return False - finally: - does_match = (WrapObj(ele_data) == ele_other) - if not does_match: - return False - - if (check_dir == CHECK_DIR.BACKWARDS): - for ele_index in range(0 - 1, -(len(self.data) + 1), -1): - # ele_index is of negative value from -1 to -len (including) - ele_data = self.data[ele_index] - # as long as not multiobj, check in backwards - if isinstance(ele_data, WrapMultiObj): - multiobj_index_backwards = ele_index - check_dir = CHECK_DIR.FORWARDS - break - else: - try: - ele_other = other[ele_index] - except IndexError: - # To_be_checked list has length less than matching list - return False - finally: - does_match = does_match = (WrapObj(ele_data) == ele_other) - if not does_match: - return False - - if (does_match and (multiobj_index_forwards is None) and (multiobj_index_forwards == multiobj_index_backwards)): - # all of data is in other - # so, if they have same length, everything is perfect - return len(self.data) == len(other) - elif (multiobj_index_forwards + (-multiobj_index_backwards)) != len(self.data): - # there is more than one length_ANY object - raise TypeError("There must be only one object of arbitary range") - else: - # there is only one length ANY object - - # prepare back index for slicing operation - # add 1 (as this one will give outer bound) and if results to 0, set to None (as 0 doesnt mean last index) - multiobj_index_backwards += 1 - multiobj_index_backwards = multiobj_index_backwards or None - ele_wrapmultiobj = self.data[multiobj_index_forwards] - logging.debug("Comparing WrapMultiObj with part of list") - does_match = (ele_wrapmultiobj == other[multiobj_index_forwards:multiobj_index_backwards]) - return does_match elif not(self.treat_as_object) and (isinstance(self.data, Mapping) and isinstance(other, Mapping)): - does_match = None # as in not known yet - for (data_key, data_value) in self.data.iteritems(): - if isinstance(data_key, type): - # todo: stop printing this line, which is broken into parts - warn(("{data_key} is a class used as a key, but it " - "wont match keys which are instances of this type" - "").format(data_key=data_key)) - try: - # try using that key - other_value = other[data_key] - except KeyError: - # if fails, it doesnt have that key. so, they dont match - does_match = False - return False - else: - does_match = (WrapObj(data_value) == other_value) - if does_match is False: - return does_match - else: - # should be True - assert(does_match is True) - return True + return self.match_dict_or_obj(other, dict) + elif self.treat_as_object: try: assert(isinstance(self.data, Mapping)) @@ -192,28 +346,16 @@ class WrapObj(object): raise TypeError("data={data} should be of type Mapping " "(e.g. dict)".format(data=self.data)) - does_match = None # as in not known yet - for (data_key, data_value) in self.data.iteritems(): - if isinstance(data_key, type): - # todo: stop printing this line, which is broken into parts - warn(("{data_key} is a class used as a key, but it " - "wont match keys which are instances of this type" - "").format(data_key=data_key)) - try: - # try using that key - other_value = other.__getattribute__(data_key) - except AttributeError: - # if fails, it doesnt have that attribute. so, they dont match - does_match = False - return False - else: - does_match = (WrapObj(data_value) == other_value) - if does_match is False: - return does_match + return self.match_dict_or_obj(other, object) + + elif check_as_value_and_type(other, self.data): + + if self.save_key is not None: + self.dict_saved_values[self.save_key] = other + return self.dict_saved_values else: - # should be True - assert(does_match is True) return True + # return True else: return False @@ -241,7 +383,8 @@ class WrapMultiObj(object): elif WrapObj([INT_OR_INF, None]) == [range_low, range_high]: range_high = range_low + 1 else: - raise TypeError("range_low and range_high must be of appropiate type") + raise TypeError("range_low={range_low} and range_high={range_high} must be of appropiate type" + "".format(range_low=range_low, range_high=range_high)) logging.debug("OK") @@ -250,12 +393,23 @@ class WrapMultiObj(object): logging.debug("DO_TYPECHECK is off") self.DO_TYPECHECK = False + self.dict_saved_values = {} + self.save_key = None + logging.debug("Create OK, returned") + def save_as(self, arg_name): + self.save_key = arg_name + return self + def __ne__(self, other): return not(self.__eq__(other)) def __eq__(self, other): + ''' + Similar to WrapObj.__eq__ + But expects other is iterable + ''' logging.debug("Checking equality of {self_data} and {other}".format(self_data=self.data, other=other)) if self.DO_TYPECHECK and (type(self.data) != type(other)): logging.debug("DO_TYPECHECK is on and yet types dont match. So returned False") @@ -278,6 +432,8 @@ class WrapMultiObj(object): else: logging.debug("OK") + self.dict_saved_values = {} + repeat_count = 0 while True: logging.debug("Running iteration {repeat_count}".format(repeat_count=repeat_count + 1)) @@ -307,8 +463,21 @@ class WrapMultiObj(object): logging.debug("Checking for repeat matching at iteration = {repeat_count} failed".format(repeat_count=repeat_count)) return False finally: - does_match = check_as_value_and_type(ele_other, ele_data) - if not does_match: + # match_dict_or_True = check_as_value_and_type(ele_other, ele_data) + match_dict_or_True = (WrapObj(ele_data) == ele_other) + # take care of save_as of child elements + if match_dict_or_True and isinstance(match_dict_or_True, dict): + # update self.dict_saved_values with each value in its list form, + # so as to allow muliple matches + for match_key in match_dict_or_True: + match_val = match_dict_or_True[match_key] + prev_match_val = self.dict_saved_values.get(match_key) + if isinstance(prev_match_val, list): + self.dict_saved_values[match_key].append(match_val) + else: + self.dict_saved_values[match_key] = [match_val] + + if not match_dict_or_True: # Return False as the match doesnt work out logging.debug("{ele_other} with {ele_data} Match retuned False") return False @@ -318,18 +487,30 @@ class WrapMultiObj(object): logging.debug("Match works with {repeat_count} iterations".format(repeat_count=repeat_count)) if self.repeat_allowed_range[0] <= repeat_count < self.repeat_allowed_range[1]: logging.debug("And it is in valid range") - return True + # take care of save_as of self + if self.save_key is not None: + self.dict_saved_values[self.save_key] = other + + return (self.dict_saved_values or True) else: logging.debug("And it is NOT in valid range") return False def check_as_value_and_type(to_check, value_or_type): - logging.debug("Checking {1} with {0}".format(value_or_type, to_check)) - # if cheking with Any, return True (let it through) + ''' + Checks whether to_check is either + i) instance of value_or_type if value_or_type is a class + ii) to_check == value_or_type if value_or_type is not a class - # if value_or_type == Any: - # return True + >>> check_as_value_and_type( 5, int) + True + >>> check_as_value_and_type( 5, 5) + True + >>> check_as_value_and_type( int, int) + False + ''' + logging.debug("Checking {1} with {0}".format(value_or_type, to_check)) # if its class, then check if instance of that if isinstance(value_or_type, type): @@ -337,12 +518,12 @@ def check_as_value_and_type(to_check, value_or_type): # try as value, if true return elif to_check == value_or_type: return True + else: + return False w = WrapObj -def fmatch(): - pass -# print(WrapObj([int, int]) == [2, 2]) -print(WrapObj([2, WrapObj([3]).times((1, 3))]) == [2, 3, 3]) -OR(int).times(5) +# to think and todo +# def fmatch(): +# pass diff --git a/wrap_obj.pyc b/wrap_obj.pyc new file mode 100644 index 0000000000000000000000000000000000000000..ceb92b0ad279173deb4e013bf93beaa27b197787 GIT binary patch literal 13610 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 literal 0 HcmV?d00001