_funcs.py 7.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212
  1. from __future__ import absolute_import, division, print_function
  2. import copy
  3. from ._compat import iteritems
  4. from ._make import NOTHING, _obj_setattr, fields
  5. from .exceptions import AttrsAttributeNotFoundError
  6. def asdict(inst, recurse=True, filter=None, dict_factory=dict,
  7. retain_collection_types=False):
  8. """
  9. Return the ``attrs`` attribute values of *inst* as a dict.
  10. Optionally recurse into other ``attrs``-decorated classes.
  11. :param inst: Instance of an ``attrs``-decorated class.
  12. :param bool recurse: Recurse into classes that are also
  13. ``attrs``-decorated.
  14. :param callable filter: A callable whose return code determines whether an
  15. attribute or element is included (``True``) or dropped (``False``). Is
  16. called with the :class:`attr.Attribute` as the first argument and the
  17. value as the second argument.
  18. :param callable dict_factory: A callable to produce dictionaries from. For
  19. example, to produce ordered dictionaries instead of normal Python
  20. dictionaries, pass in ``collections.OrderedDict``.
  21. :param bool retain_collection_types: Do not convert to ``list`` when
  22. encountering an attribute whose type is ``tuple`` or ``set``. Only
  23. meaningful if ``recurse`` is ``True``.
  24. :rtype: return type of *dict_factory*
  25. :raise attr.exceptions.NotAnAttrsClassError: If *cls* is not an ``attrs``
  26. class.
  27. .. versionadded:: 16.0.0 *dict_factory*
  28. .. versionadded:: 16.1.0 *retain_collection_types*
  29. """
  30. attrs = fields(inst.__class__)
  31. rv = dict_factory()
  32. for a in attrs:
  33. v = getattr(inst, a.name)
  34. if filter is not None and not filter(a, v):
  35. continue
  36. if recurse is True:
  37. if has(v.__class__):
  38. rv[a.name] = asdict(v, recurse=True, filter=filter,
  39. dict_factory=dict_factory)
  40. elif isinstance(v, (tuple, list, set)):
  41. cf = v.__class__ if retain_collection_types is True else list
  42. rv[a.name] = cf([
  43. asdict(i, recurse=True, filter=filter,
  44. dict_factory=dict_factory)
  45. if has(i.__class__) else i
  46. for i in v
  47. ])
  48. elif isinstance(v, dict):
  49. df = dict_factory
  50. rv[a.name] = df((
  51. asdict(kk, dict_factory=df) if has(kk.__class__) else kk,
  52. asdict(vv, dict_factory=df) if has(vv.__class__) else vv)
  53. for kk, vv in iteritems(v))
  54. else:
  55. rv[a.name] = v
  56. else:
  57. rv[a.name] = v
  58. return rv
  59. def astuple(inst, recurse=True, filter=None, tuple_factory=tuple,
  60. retain_collection_types=False):
  61. """
  62. Return the ``attrs`` attribute values of *inst* as a tuple.
  63. Optionally recurse into other ``attrs``-decorated classes.
  64. :param inst: Instance of an ``attrs``-decorated class.
  65. :param bool recurse: Recurse into classes that are also
  66. ``attrs``-decorated.
  67. :param callable filter: A callable whose return code determines whether an
  68. attribute or element is included (``True``) or dropped (``False``). Is
  69. called with the :class:`attr.Attribute` as the first argument and the
  70. value as the second argument.
  71. :param callable tuple_factory: A callable to produce tuples from. For
  72. example, to produce lists instead of tuples.
  73. :param bool retain_collection_types: Do not convert to ``list``
  74. or ``dict`` when encountering an attribute which type is
  75. ``tuple``, ``dict`` or ``set``. Only meaningful if ``recurse`` is
  76. ``True``.
  77. :rtype: return type of *tuple_factory*
  78. :raise attr.exceptions.NotAnAttrsClassError: If *cls* is not an ``attrs``
  79. class.
  80. .. versionadded:: 16.2.0
  81. """
  82. attrs = fields(inst.__class__)
  83. rv = []
  84. retain = retain_collection_types # Very long. :/
  85. for a in attrs:
  86. v = getattr(inst, a.name)
  87. if filter is not None and not filter(a, v):
  88. continue
  89. if recurse is True:
  90. if has(v.__class__):
  91. rv.append(astuple(v, recurse=True, filter=filter,
  92. tuple_factory=tuple_factory,
  93. retain_collection_types=retain))
  94. elif isinstance(v, (tuple, list, set)):
  95. cf = v.__class__ if retain is True else list
  96. rv.append(cf([
  97. astuple(j, recurse=True, filter=filter,
  98. tuple_factory=tuple_factory,
  99. retain_collection_types=retain)
  100. if has(j.__class__) else j
  101. for j in v
  102. ]))
  103. elif isinstance(v, dict):
  104. df = v.__class__ if retain is True else dict
  105. rv.append(df(
  106. (
  107. astuple(
  108. kk,
  109. tuple_factory=tuple_factory,
  110. retain_collection_types=retain
  111. ) if has(kk.__class__) else kk,
  112. astuple(
  113. vv,
  114. tuple_factory=tuple_factory,
  115. retain_collection_types=retain
  116. ) if has(vv.__class__) else vv
  117. )
  118. for kk, vv in iteritems(v)))
  119. else:
  120. rv.append(v)
  121. else:
  122. rv.append(v)
  123. return rv if tuple_factory is list else tuple_factory(rv)
  124. def has(cls):
  125. """
  126. Check whether *cls* is a class with ``attrs`` attributes.
  127. :param type cls: Class to introspect.
  128. :raise TypeError: If *cls* is not a class.
  129. :rtype: :class:`bool`
  130. """
  131. return getattr(cls, "__attrs_attrs__", None) is not None
  132. def assoc(inst, **changes):
  133. """
  134. Copy *inst* and apply *changes*.
  135. :param inst: Instance of a class with ``attrs`` attributes.
  136. :param changes: Keyword changes in the new copy.
  137. :return: A copy of inst with *changes* incorporated.
  138. :raise attr.exceptions.AttrsAttributeNotFoundError: If *attr_name* couldn't
  139. be found on *cls*.
  140. :raise attr.exceptions.NotAnAttrsClassError: If *cls* is not an ``attrs``
  141. class.
  142. .. deprecated:: 17.1.0
  143. Use :func:`evolve` instead.
  144. """
  145. import warnings
  146. warnings.warn("assoc is deprecated and will be removed after 2018/01.",
  147. DeprecationWarning, stacklevel=2)
  148. new = copy.copy(inst)
  149. attrs = fields(inst.__class__)
  150. for k, v in iteritems(changes):
  151. a = getattr(attrs, k, NOTHING)
  152. if a is NOTHING:
  153. raise AttrsAttributeNotFoundError(
  154. "{k} is not an attrs attribute on {cl}."
  155. .format(k=k, cl=new.__class__)
  156. )
  157. _obj_setattr(new, k, v)
  158. return new
  159. def evolve(inst, **changes):
  160. """
  161. Create a new instance, based on *inst* with *changes* applied.
  162. :param inst: Instance of a class with ``attrs`` attributes.
  163. :param changes: Keyword changes in the new copy.
  164. :return: A copy of inst with *changes* incorporated.
  165. :raise TypeError: If *attr_name* couldn't be found in the class
  166. ``__init__``.
  167. :raise attr.exceptions.NotAnAttrsClassError: If *cls* is not an ``attrs``
  168. class.
  169. .. versionadded:: 17.1.0
  170. """
  171. cls = inst.__class__
  172. attrs = fields(cls)
  173. for a in attrs:
  174. if not a.init:
  175. continue
  176. attr_name = a.name # To deal with private attributes.
  177. init_name = attr_name if attr_name[0] != "_" else attr_name[1:]
  178. if init_name not in changes:
  179. changes[init_name] = getattr(inst, attr_name)
  180. return cls(**changes)