Skip to content

Instantly share code, notes, and snippets.

@guyjacks
Last active December 22, 2017 04:13
Show Gist options
  • Save guyjacks/246244d819a001b60576d5c29b22d1d3 to your computer and use it in GitHub Desktop.
Save guyjacks/246244d819a001b60576d5c29b22d1d3 to your computer and use it in GitHub Desktop.
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