Last active
December 22, 2017 04:13
-
-
Save guyjacks/246244d819a001b60576d5c29b22d1d3 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
from collections.abc import Mapping | |
import abc | |
""" | |
FEATURE IDEAS | |
add a report() method to that prints a nicely formatted output of how | |
each comparison went. | |
assert a == b | |
print(a.report()) | |
""" | |
class PseudoDict(Mapping): | |
def __init__(self, *args, **kwargs): | |
self.dict = dict(*args, **kwargs) | |
def __getitem__(self, key): | |
return self.dict[key] | |
def __iter__(self): | |
return iter(self.dict) | |
def __len__(self): | |
return len(self.dict) | |
def __repr__(self): | |
return repr(self.dict) | |
def __str__(self): | |
return str(self.dict) | |
class PseudoObject: | |
def __init__(self, **properties): | |
self.properties = properties | |
def __getattr__(self, item): | |
return self.properties[item] | |
def as_dict(self): | |
return self.properties | |
def __eq__(self, other): | |
""" | |
:param other: | |
:return: | |
""" | |
if other == None or other == False: | |
return False | |
else: | |
return self.properties == other.properties | |
def __str__(self): | |
return repr(self) | |
def __repr__(self): | |
result = [ | |
# explore f-strings (new in python 3.6) | |
# f'{key}={value!r}' | |
'{}={!r}'.format(key, value) | |
for key, value in sorted(self.properties.items()) | |
] | |
result = [] | |
for key, value in sorted(self.properties.items()): | |
result.append('{}={!r}'.format(key, value)) | |
s = sorted(self.properties.keys()) | |
result = [] | |
# for key, value in sorted(self.properties.items()): | |
for key in s: | |
value = self.properties[key] | |
# result.append('{}={!r}'.format(key, self.properties[key])) | |
if isinstance(value, str): | |
result.append('{}="{}"'.format(key, self.properties[key])) | |
else: | |
result.append('{}={}'.format(key, self.properties[key])) | |
if not result: | |
result = ['empty'] | |
return '<PseudoObject: {}>'.format(','.join(result)) | |
if len(result) > 0: | |
return '<PseudoObject: {}>'.format(','.join(result)) | |
else: | |
return '<PseudoObject: empty>' | |
if result: | |
return '<PseudoObject: {}>'.format(','.join(result)) | |
else: | |
return '<PseudoObject: empty>' | |
return '<PseudoObject: {}>'.format(','.join(result) or 'empty') | |
stuff = ','.join(result) | |
return '<PseudoObject: {}>'.format(stuff or 'empty') | |
return '<PseudoObject: {}>'.format( | |
','.join(result) or 'empty' | |
) | |
a if b else c | |
b ? a : c | |
class CompareBot: | |
def __init__(self, instance): | |
""" | |
Wraps the instance so it can easily be compared to PseudoObjects | |
:param instance: | |
""" | |
self.instance = instance | |
""" | |
Store the simplified dict representation of the instance. This typically | |
gets created during a comparison with a PseudoObject. It will contain only | |
keys which correspond to properties of the PseudoObject to which this | |
CompareBot is compared. | |
""" | |
self.simplified = {} | |
""" | |
Stores the last Pseudo Thing to which this CompareBot was compared. | |
Its used by __repr__ to only show the properties relevant to the pseudo thing. | |
""" | |
self.last_compared_with = None | |
@abc.abstractmethod | |
def simplify(self, other): | |
pass | |
@abc.abstractmethod | |
def get_attribute(self, name): | |
pass | |
def __eq__(self, other): | |
""" | |
:param other: expects a PseudoObject or PseudoDict | |
:return: | |
""" | |
self.simplify(other) | |
self.last_compared_with = other | |
return self.simplified == other.as_dict() | |
def __ne__(self, other): | |
# need to make sure that __eq__ gets called so that last_compared_with | |
# is set. Not sure if the default implementation of __ne__ just | |
# calls __eq__ with not in front of it. I suppose I could check. If | |
# it does, then I don't need to implement this | |
return not self.__eq__(other) | |
def reset(self): | |
self.simplified = {} | |
self.last_compared_with = None | |
def __get_instance_class(self): | |
return self.instance.__class__.__name__ | |
def __str__(self): | |
return repr(self) | |
def __repr__(self): | |
""" | |
:return: returns a string representation relevant to last comparison | |
i.e. When compared to pseudo object containing with two properties | |
then only return a string representation of self with those two | |
properties | |
""" | |
if self.last_compared_with: | |
s = sorted(self.last_compared_with.properties.keys()) | |
result = [] | |
for property in s: | |
property_value = self.get_attribute(property) | |
if isinstance(property_value, str): | |
result.append('{}="{}"'.format(property, property_value)) | |
else: | |
result.append('{}={}'.format(property, str(property_value))) | |
class_name = self.__get_instance_class() | |
return '<{}: {}>'.format(class_name, ','.join(result)) | |
else: | |
return repr(self.instance) | |
class ObjectCompareBot(CompareBot): | |
def __init__(self, the_object): | |
super().__init__(the_object) | |
def simplify(self, the_pseudo_object): | |
""" | |
Stores a dictionary comprised of keys for each property of the supplied | |
pseudo object and the property values of self.instance corresponding | |
to those keys | |
:param the_pseudo_object: | |
:return: | |
""" | |
self.reset() | |
for property, value in the_pseudo_object.properties.items(): | |
self.simplified[property] = getattr(self.instance, property, None) | |
def get_attribute(self, name): | |
return getattr(self.instance, name, None) | |
class DictCompareBot(CompareBot): | |
# BIG NOTE TO SELF | |
# Maybe I should have BasicDictCompareBot that can work with the built-in | |
# instead of the PseudoDict. I can implement an AdvancedDictCompareBot | |
# that expects a PseudoDict. I think this would make the most sense. I | |
# also don't have to worry about breaking backwards compatibility when I | |
# build the AdvancedDictCompareBot. | |
# compare a dictionary to a pseudo dict with partial vals | |
def __init__(self, the_pseudo_dict): | |
pass | |
def simplify(self, the_pseudo_dict): | |
pass | |
def get_attribute(self, name): | |
pass | |
class CollectionCompareBot: | |
def __init__(self, collection, ordered=False): | |
self.collection = collection | |
# Enable ordered if the order of items in two collections | |
# is important for determining equivalence | |
self.ordered = ordered | |
def __with_compare_bot(self, item): | |
return ObjectCompareBot(item) | |
def __compare_ordered(self, other): | |
""" | |
:param other: assumed to have same length as self.collection | |
:return: | |
""" | |
zipped = zip(self.collection, other) | |
for a, b in zipped: | |
if self.__with_compare_bot(a) != b: | |
return False | |
return True | |
def __remove_from_list(self, the_list, the_item): | |
""" | |
Remove the item from the list or return None if the item does not exist | |
:param the_list: | |
:param the_item: | |
:return: | |
""" | |
# https://stackoverflow.com/questions/11520492/difference-between-del-remove-and-pop-on-lists | |
cb = self.__with_compare_bot(the_item) | |
for idx, member in enumerate(the_list): | |
# member is the pseudo thing | |
if cb == member: | |
return the_list.pop(idx) | |
return None | |
def __compare_unordered(self, other): | |
""" | |
:param other: assumed to have same length as self.collection | |
:return: | |
""" | |
# Do not alter the supplied list | |
mutable = list(other) | |
for item in self.collection: | |
removed = self.__remove_from_list(mutable, item) | |
if removed == None: | |
return False | |
return True | |
def __eq__(self, other): | |
""" | |
Compares all the items in collection a to those in the other collection | |
:param other: Assumed to be a collection of pseudo objects or dicts | |
:return: True if all items are equal | |
""" | |
# They must be the same length | |
if len(self.collection) != len(other): | |
return False | |
if self.ordered: | |
return self.__compare_ordered(other) | |
else: | |
return self.__compare_unordered(other) | |
def contains(self, *items): | |
pass | |
def does_not_contain(self, *items): | |
pass | |
def __str__(self): | |
return str(self.collection) | |
def __repr__(self): | |
return repr(self.collection) | |
class OrderedCollectionCompareBot(CollectionCompareBot): | |
def __init__(self, collection): | |
super().__init__(collection, ordered=True) | |
class UnorderedCollectionCompareBot(CollectionCompareBot): | |
def __init__(self, collection): | |
super().__init__(collection, ordered=False) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment