objects.py 4.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143
  1. # -*- coding: utf-8 -*-
  2. """Object related utilities, including introspection, etc."""
  3. from __future__ import absolute_import, unicode_literals
  4. from functools import reduce
  5. __all__ = ('Bunch', 'FallbackContext', 'getitem_property', 'mro_lookup')
  6. class Bunch(object):
  7. """Object that enables you to modify attributes."""
  8. def __init__(self, **kwargs):
  9. self.__dict__.update(kwargs)
  10. def mro_lookup(cls, attr, stop=set(), monkey_patched=[]):
  11. """Return the first node by MRO order that defines an attribute.
  12. Arguments:
  13. cls (Any): Child class to traverse.
  14. attr (str): Name of attribute to find.
  15. stop (Set[Any]): A set of types that if reached will stop
  16. the search.
  17. monkey_patched (Sequence): Use one of the stop classes
  18. if the attributes module origin isn't in this list.
  19. Used to detect monkey patched attributes.
  20. Returns:
  21. Any: The attribute value, or :const:`None` if not found.
  22. """
  23. for node in cls.mro():
  24. if node in stop:
  25. try:
  26. value = node.__dict__[attr]
  27. module_origin = value.__module__
  28. except (AttributeError, KeyError):
  29. pass
  30. else:
  31. if module_origin not in monkey_patched:
  32. return node
  33. return
  34. if attr in node.__dict__:
  35. return node
  36. class FallbackContext(object):
  37. """Context workaround.
  38. The built-in ``@contextmanager`` utility does not work well
  39. when wrapping other contexts, as the traceback is wrong when
  40. the wrapped context raises.
  41. This solves this problem and can be used instead of ``@contextmanager``
  42. in this example::
  43. @contextmanager
  44. def connection_or_default_connection(connection=None):
  45. if connection:
  46. # user already has a connection, shouldn't close
  47. # after use
  48. yield connection
  49. else:
  50. # must've new connection, and also close the connection
  51. # after the block returns
  52. with create_new_connection() as connection:
  53. yield connection
  54. This wrapper can be used instead for the above like this::
  55. def connection_or_default_connection(connection=None):
  56. return FallbackContext(connection, create_new_connection)
  57. """
  58. def __init__(self, provided, fallback, *fb_args, **fb_kwargs):
  59. self.provided = provided
  60. self.fallback = fallback
  61. self.fb_args = fb_args
  62. self.fb_kwargs = fb_kwargs
  63. self._context = None
  64. def __enter__(self):
  65. if self.provided is not None:
  66. return self.provided
  67. context = self._context = self.fallback(
  68. *self.fb_args, **self.fb_kwargs
  69. ).__enter__()
  70. return context
  71. def __exit__(self, *exc_info):
  72. if self._context is not None:
  73. return self._context.__exit__(*exc_info)
  74. class getitem_property(object):
  75. """Attribute -> dict key descriptor.
  76. The target object must support ``__getitem__``,
  77. and optionally ``__setitem__``.
  78. Example:
  79. >>> from collections import defaultdict
  80. >>> class Me(dict):
  81. ... deep = defaultdict(dict)
  82. ...
  83. ... foo = _getitem_property('foo')
  84. ... deep_thing = _getitem_property('deep.thing')
  85. >>> me = Me()
  86. >>> me.foo
  87. None
  88. >>> me.foo = 10
  89. >>> me.foo
  90. 10
  91. >>> me['foo']
  92. 10
  93. >>> me.deep_thing = 42
  94. >>> me.deep_thing
  95. 42
  96. >>> me.deep
  97. defaultdict(<type 'dict'>, {'thing': 42})
  98. """
  99. def __init__(self, keypath, doc=None):
  100. path, _, self.key = keypath.rpartition('.')
  101. self.path = path.split('.') if path else None
  102. self.__doc__ = doc
  103. def _path(self, obj):
  104. return (reduce(lambda d, k: d[k], [obj] + self.path) if self.path
  105. else obj)
  106. def __get__(self, obj, type=None):
  107. if obj is None:
  108. return type
  109. return self._path(obj).get(self.key)
  110. def __set__(self, obj, value):
  111. self._path(obj)[self.key] = value