Source code for callee.attributes

"""
Attribute-based matchers.
"""
from itertools import starmap
from operator import itemgetter

from callee.base import BaseMatcher, Eq


__all__ = [
    'Attrs', 'Attr', 'HasAttrs', 'HasAttr',
]


[docs]class Attrs(BaseMatcher): """Matches objects based on their attributes. To match successfully, the object needs to: * have all the attributes whose names were passed as positional arguments (regardless of their values) * have the attribute names/values that correspond exactly to keyword arguments' names and values Examples:: Attrs('foo') # `foo` attribute with any value Attrs('foo', 'bar') # `foo` and `bar` attributes with any values Attrs(foo=42) # `foo` attribute with value of 42 Attrs(bar=Integer()) # `bar` attribute whose value is an integer Attrs('foo', bar='x') # `foo` with any value, `bar` with value of 'x' """ def __init__(self, *args, **kwargs): if not (args or kwargs): raise TypeError("%s() requires at least one argument" % ( self.__class__.__name__,)) self.attr_names = list(args) self.attr_dict = dict((k, v if isinstance(v, BaseMatcher) else Eq(v)) for k, v in kwargs.items()) def match(self, value): for name in self.attr_names: # Can't use hasattr() here because it swallows *all* exceptions # from attribute access in Python 2.x, not just AttributeError. # More details: https://hynek.me/articles/hasattr/ try: getattr(value, name) except AttributeError: return False for name, matcher in self.attr_dict.items(): # Separately handle retrieving of the attribute value, # so that any stray AttributeErrors from the matcher itself # are correctly propagated. try: attrvalue = getattr(value, name) except AttributeError: return False if not matcher.match(attrvalue): return False return True def __repr__(self): """Return a representation of the matcher.""" # get both the names-only and valued attributes and sort them by name sentinel = object() attrs = [(name, sentinel) for name in self.attr_names] + list(self.attr_dict.items()) attrs.sort(key=itemgetter(0)) def attr_repr(name, value): # include the value with attribute name whenever necessary if value is sentinel: return name value = value.value if isinstance(value, Eq) else value return "%s=%r" % (name, value) return "<%s %s>" % (self.__class__.__name__, " ".join(starmap(attr_repr, attrs)))
class Attr(Attrs): """Matches objects that have an attribute with given name and value, as given by a keyword argument. """ def __init__(self, **kwargs): if not len(kwargs) == 1: raise TypeError("Attr() requires exactly one keyword argument") super(Attr, self).__init__(**kwargs)
[docs]class HasAttrs(Attrs): """Matches objects that have all of the specified attribute names, regardless of their values. """ def __init__(self, *args): super(HasAttrs, self).__init__(*args)
class HasAttr(HasAttrs): """Matches object that have an attribute with given name, regardless of its value. """ def __init__(self, name): super(HasAttr, self).__init__(name)