Source code for pytoolbox.types

import inspect, itertools
from collections import abc

from . import module
from .collections import merge_dicts

_all = module.All(globals())


[docs]def get_arguments_names(function): """ Return a list with arguments names. >>> from pytoolbox import types >>> >>> get_arguments_names(get_arguments_names) ['function'] >>> >>> def my_func(directory, a=1, *args, b, c=None, **kwargs): ... pass ... >>> get_arguments_names(my_func) ['directory', 'a', 'args', 'b', 'c', 'kwargs'] >>> >>> get_arguments_names(types.get_subclasses) ['obj', 'nested'] >>> get_arguments_names(types.merge_bases_attribute) ['cls', 'attr_name', 'init', 'default', 'merge_func'] """ return list(inspect.signature(function).parameters.keys())
[docs]def get_properties(obj): return ( (n, getattr(obj, n)) for n, p in inspect.getmembers(obj.__class__, lambda m: isinstance(m, property)) )
[docs]def get_slots(obj): """Return a set with the `__slots__` of the `obj` including all parent classes `__slots__`.""" return set(itertools.chain.from_iterable( getattr(cls, '__slots__', ()) for cls in obj.__class__.__mro__) )
[docs]def get_subclasses(obj, nested=True): """ Walk the inheritance tree of ``obj``. Yield tuples with (class, subclasses). **Example usage** >>> class Root(object): ... pass ... >>> class NodeA(Root): ... pass ... >>> class NodeB(Root): ... pass ... >>> class NodeC(NodeA): ... pass ... >>> class NodeD(NodeA): ... pass ... >>> class NodeE(NodeD): ... pass ... >>> [(c.__name__, bool(s)) for c, s in get_subclasses(Root)] [('NodeA', True), ('NodeC', False), ('NodeD', True), ('NodeE', False), ('NodeB', False)] >>> [(c.__name__, bool(s)) for c, s in get_subclasses(Root, nested=False)] [('NodeA', True), ('NodeB', False)] >>> [(c.__name__, bool(s)) for c, s in get_subclasses(NodeB)] [] >>> [(c.__name__, bool(s)) for c, s in get_subclasses(NodeD)] [('NodeE', False)] """ for subclass in obj.__subclasses__(): yield subclass, subclass.__subclasses__() if nested: for sub_subclass in get_subclasses(subclass, nested): yield sub_subclass
[docs]def isiterable(obj, blacklist=(bytes, str)): """ Return ``True`` if the object is an iterable, but ``False`` for any class in `blacklist`. **Example usage** >>> from pytoolbox.unittest import asserts >>> for obj in b'binary', 'unicode', 42: ... asserts.false(isiterable(obj), obj) >>> for obj in [], (), set(), iter({}.items()): ... asserts.true(isiterable(obj), obj) >>> isiterable({}, dict) False """ return isinstance(obj, abc.Iterable) and not isinstance(obj, blacklist)
[docs]def merge_annotations(cls: type): """ Merge annotations defined in all bases classes (using `__mro__`) into given `cls`. Can be used as a decorator. **Example usage** >>> class Point2D(object): ... x: int ... y: int ... >>> class Point3D(Point2D): ... z: int ... >>> class Point4D(Point3D, Point2D): ... w: int ... >>> @merge_annotations ... class Point4X(Point4D): ... x: float ... other: str ... >>> assert Point2D.__annotations__ == {'x': int, 'y': int} >>> assert Point3D.__annotations__ == {'z': int} >>> assert Point4D.__annotations__ == {'w': int} >>> assert Point4X.__annotations__ == {'x': float, 'y': int, 'z': int, 'w': int, 'other': str} """ cls.__annotations__ = merge_dicts(*[ getattr(base, '__annotations__', {}) for base in reversed(cls.__mro__) ]) return cls
[docs]def merge_bases_attribute(cls, attr_name, init, default, merge_func=lambda a, b: a + b): """ Merge all values of attribute defined in all bases classes (using `__mro__`). Return resulting value. Use default every time a class does not have given attribute. Be careful, `merge_func` must be a pure function. """ value = init for base in cls.__mro__: value = merge_func(value, getattr(base, attr_name, default)) return value
[docs]class DummyObject(object): # pylint:disable=too-few-public-methods """ Easy way to generate a dynamic object with the attributes defined at instantiation. **Example usage** >>> obj = DummyObject(foo=42, bar=None) >>> obj.foo 42 >>> obj.bar is None True """
[docs] def __init__(self, **kwargs): self.__dict__.update(kwargs)
[docs]class EchoObject(object): """ Object that return any missing attribute as an instance of :class:`EchoObject` with the name set to the Python expression used to access it. Also implements __getitem__. Some examples are worth hundred words... **Example usage** >>> from pytoolbox.unittest import asserts >>> something = EchoObject('something', language='Python') >>> something._name 'something' >>> something.language 'Python' >>> hasattr(something, 'everything') True >>> type(something.user.email) <class 'pytoolbox.types.EchoObject'> >>> str(something.user.first_name) 'something.user.first_name' >>> str(something[0][None]['bar']) "something[0][None]['bar']" >>> str(something[0].node['foo'].x) "something[0].node['foo'].x" >>> str(something) 'something' You can also define the class for the generated attributes: >>> something.attr_class = list >>> type(something.cool) <class 'list'> This class handles sub-classing appropriately: >>> class MyEchoObject(EchoObject): ... pass >>> >>> type(MyEchoObject('name').x.y.z) <class 'pytoolbox.types.MyEchoObject'> """ attr_class = None
[docs] def __init__(self, name, **attrs): assert '_name' not in attrs self.__dict__.update(attrs) self._name = name
def __getattr__(self, name): return (self.attr_class or self.__class__)(f'{self._name}.{name}') def __getitem__(self, key): return (self.attr_class or self.__class__)(f'{self._name}[{repr(key)}]') def __str__(self): return self._name
[docs]class EchoDict(dict): """ Dictionary that return any missing item as an instance of :class:`EchoObject` with the name set to the Python expression used to access it. Some examples are worth hundred words... **Example usage** >>> context = EchoDict('context', language='Python') >>> context._name 'context' >>> context['language'] 'Python' >>> 'anything' in context True >>> str(context['user'].first_name) "context['user'].first_name" >>> str(context[0][None]['bar']) "context[0][None]['bar']" >>> str(context[0].node['foo'].x) "context[0].node['foo'].x" You can also define the class for the generated items: >>> context.item_class = set >>> type(context['jet']) <class 'set'> """ item_class = EchoObject
[docs] def __init__(self, name, **items): assert '_name' not in items super().__init__(**items) self._name = name
def __contains__(self, key): """Always return True because missing items are generated.""" return True def __getitem__(self, key): try: return super().__getitem__(key) except KeyError: return self.item_class(f'{self._name}[{repr(key)}]')
[docs]class MissingType(object): def __copy__(self): return self def __deepcopy__(self, memo): return self def __bool__(self): return False def __repr__(self): return 'Missing'
Missing = MissingType() __all__ = _all.diff(globals())