mercurial/thirdparty/attr/_funcs.py
changeset 49643 e1c586b9a43c
parent 34397 765eb17a7eb8
equal deleted inserted replaced
49642:7e6f3c69c0fb 49643:e1c586b9a43c
     1 from __future__ import absolute_import, division, print_function
     1 # SPDX-License-Identifier: MIT
       
     2 
     2 
     3 
     3 import copy
     4 import copy
     4 
     5 
     5 from ._compat import iteritems
     6 from ._make import NOTHING, _obj_setattr, fields
     6 from ._make import NOTHING, fields, _obj_setattr
       
     7 from .exceptions import AttrsAttributeNotFoundError
     7 from .exceptions import AttrsAttributeNotFoundError
     8 
     8 
     9 
     9 
    10 def asdict(inst, recurse=True, filter=None, dict_factory=dict,
    10 def asdict(
    11            retain_collection_types=False):
    11     inst,
       
    12     recurse=True,
       
    13     filter=None,
       
    14     dict_factory=dict,
       
    15     retain_collection_types=False,
       
    16     value_serializer=None,
       
    17 ):
    12     """
    18     """
    13     Return the ``attrs`` attribute values of *inst* as a dict.
    19     Return the ``attrs`` attribute values of *inst* as a dict.
    14 
    20 
    15     Optionally recurse into other ``attrs``-decorated classes.
    21     Optionally recurse into other ``attrs``-decorated classes.
    16 
    22 
    17     :param inst: Instance of an ``attrs``-decorated class.
    23     :param inst: Instance of an ``attrs``-decorated class.
    18     :param bool recurse: Recurse into classes that are also
    24     :param bool recurse: Recurse into classes that are also
    19         ``attrs``-decorated.
    25         ``attrs``-decorated.
    20     :param callable filter: A callable whose return code deteremines whether an
    26     :param callable filter: A callable whose return code determines whether an
    21         attribute or element is included (``True``) or dropped (``False``).  Is
    27         attribute or element is included (``True``) or dropped (``False``).  Is
    22         called with the :class:`attr.Attribute` as the first argument and the
    28         called with the `attrs.Attribute` as the first argument and the
    23         value as the second argument.
    29         value as the second argument.
    24     :param callable dict_factory: A callable to produce dictionaries from.  For
    30     :param callable dict_factory: A callable to produce dictionaries from.  For
    25         example, to produce ordered dictionaries instead of normal Python
    31         example, to produce ordered dictionaries instead of normal Python
    26         dictionaries, pass in ``collections.OrderedDict``.
    32         dictionaries, pass in ``collections.OrderedDict``.
    27     :param bool retain_collection_types: Do not convert to ``list`` when
    33     :param bool retain_collection_types: Do not convert to ``list`` when
    28         encountering an attribute whose type is ``tuple`` or ``set``.  Only
    34         encountering an attribute whose type is ``tuple`` or ``set``.  Only
    29         meaningful if ``recurse`` is ``True``.
    35         meaningful if ``recurse`` is ``True``.
       
    36     :param Optional[callable] value_serializer: A hook that is called for every
       
    37         attribute or dict key/value.  It receives the current instance, field
       
    38         and value and must return the (updated) value.  The hook is run *after*
       
    39         the optional *filter* has been applied.
    30 
    40 
    31     :rtype: return type of *dict_factory*
    41     :rtype: return type of *dict_factory*
    32 
    42 
    33     :raise attr.exceptions.NotAnAttrsClassError: If *cls* is not an ``attrs``
    43     :raise attr.exceptions.NotAnAttrsClassError: If *cls* is not an ``attrs``
    34         class.
    44         class.
    35 
    45 
    36     ..  versionadded:: 16.0.0 *dict_factory*
    46     ..  versionadded:: 16.0.0 *dict_factory*
    37     ..  versionadded:: 16.1.0 *retain_collection_types*
    47     ..  versionadded:: 16.1.0 *retain_collection_types*
       
    48     ..  versionadded:: 20.3.0 *value_serializer*
       
    49     ..  versionadded:: 21.3.0 If a dict has a collection for a key, it is
       
    50         serialized as a tuple.
    38     """
    51     """
    39     attrs = fields(inst.__class__)
    52     attrs = fields(inst.__class__)
    40     rv = dict_factory()
    53     rv = dict_factory()
    41     for a in attrs:
    54     for a in attrs:
    42         v = getattr(inst, a.name)
    55         v = getattr(inst, a.name)
    43         if filter is not None and not filter(a, v):
    56         if filter is not None and not filter(a, v):
    44             continue
    57             continue
       
    58 
       
    59         if value_serializer is not None:
       
    60             v = value_serializer(inst, a, v)
       
    61 
    45         if recurse is True:
    62         if recurse is True:
    46             if has(v.__class__):
    63             if has(v.__class__):
    47                 rv[a.name] = asdict(v, recurse=True, filter=filter,
    64                 rv[a.name] = asdict(
    48                                     dict_factory=dict_factory)
    65                     v,
    49             elif isinstance(v, (tuple, list, set)):
    66                     recurse=True,
       
    67                     filter=filter,
       
    68                     dict_factory=dict_factory,
       
    69                     retain_collection_types=retain_collection_types,
       
    70                     value_serializer=value_serializer,
       
    71                 )
       
    72             elif isinstance(v, (tuple, list, set, frozenset)):
    50                 cf = v.__class__ if retain_collection_types is True else list
    73                 cf = v.__class__ if retain_collection_types is True else list
    51                 rv[a.name] = cf([
    74                 rv[a.name] = cf(
    52                     asdict(i, recurse=True, filter=filter,
    75                     [
    53                            dict_factory=dict_factory)
    76                         _asdict_anything(
    54                     if has(i.__class__) else i
    77                             i,
    55                     for i in v
    78                             is_key=False,
    56                 ])
    79                             filter=filter,
       
    80                             dict_factory=dict_factory,
       
    81                             retain_collection_types=retain_collection_types,
       
    82                             value_serializer=value_serializer,
       
    83                         )
       
    84                         for i in v
       
    85                     ]
       
    86                 )
    57             elif isinstance(v, dict):
    87             elif isinstance(v, dict):
    58                 df = dict_factory
    88                 df = dict_factory
    59                 rv[a.name] = df((
    89                 rv[a.name] = df(
    60                     asdict(kk, dict_factory=df) if has(kk.__class__) else kk,
    90                     (
    61                     asdict(vv, dict_factory=df) if has(vv.__class__) else vv)
    91                         _asdict_anything(
    62                     for kk, vv in iteritems(v))
    92                             kk,
       
    93                             is_key=True,
       
    94                             filter=filter,
       
    95                             dict_factory=df,
       
    96                             retain_collection_types=retain_collection_types,
       
    97                             value_serializer=value_serializer,
       
    98                         ),
       
    99                         _asdict_anything(
       
   100                             vv,
       
   101                             is_key=False,
       
   102                             filter=filter,
       
   103                             dict_factory=df,
       
   104                             retain_collection_types=retain_collection_types,
       
   105                             value_serializer=value_serializer,
       
   106                         ),
       
   107                     )
       
   108                     for kk, vv in v.items()
       
   109                 )
    63             else:
   110             else:
    64                 rv[a.name] = v
   111                 rv[a.name] = v
    65         else:
   112         else:
    66             rv[a.name] = v
   113             rv[a.name] = v
    67     return rv
   114     return rv
    68 
   115 
    69 
   116 
    70 def astuple(inst, recurse=True, filter=None, tuple_factory=tuple,
   117 def _asdict_anything(
    71             retain_collection_types=False):
   118     val,
       
   119     is_key,
       
   120     filter,
       
   121     dict_factory,
       
   122     retain_collection_types,
       
   123     value_serializer,
       
   124 ):
       
   125     """
       
   126     ``asdict`` only works on attrs instances, this works on anything.
       
   127     """
       
   128     if getattr(val.__class__, "__attrs_attrs__", None) is not None:
       
   129         # Attrs class.
       
   130         rv = asdict(
       
   131             val,
       
   132             recurse=True,
       
   133             filter=filter,
       
   134             dict_factory=dict_factory,
       
   135             retain_collection_types=retain_collection_types,
       
   136             value_serializer=value_serializer,
       
   137         )
       
   138     elif isinstance(val, (tuple, list, set, frozenset)):
       
   139         if retain_collection_types is True:
       
   140             cf = val.__class__
       
   141         elif is_key:
       
   142             cf = tuple
       
   143         else:
       
   144             cf = list
       
   145 
       
   146         rv = cf(
       
   147             [
       
   148                 _asdict_anything(
       
   149                     i,
       
   150                     is_key=False,
       
   151                     filter=filter,
       
   152                     dict_factory=dict_factory,
       
   153                     retain_collection_types=retain_collection_types,
       
   154                     value_serializer=value_serializer,
       
   155                 )
       
   156                 for i in val
       
   157             ]
       
   158         )
       
   159     elif isinstance(val, dict):
       
   160         df = dict_factory
       
   161         rv = df(
       
   162             (
       
   163                 _asdict_anything(
       
   164                     kk,
       
   165                     is_key=True,
       
   166                     filter=filter,
       
   167                     dict_factory=df,
       
   168                     retain_collection_types=retain_collection_types,
       
   169                     value_serializer=value_serializer,
       
   170                 ),
       
   171                 _asdict_anything(
       
   172                     vv,
       
   173                     is_key=False,
       
   174                     filter=filter,
       
   175                     dict_factory=df,
       
   176                     retain_collection_types=retain_collection_types,
       
   177                     value_serializer=value_serializer,
       
   178                 ),
       
   179             )
       
   180             for kk, vv in val.items()
       
   181         )
       
   182     else:
       
   183         rv = val
       
   184         if value_serializer is not None:
       
   185             rv = value_serializer(None, None, rv)
       
   186 
       
   187     return rv
       
   188 
       
   189 
       
   190 def astuple(
       
   191     inst,
       
   192     recurse=True,
       
   193     filter=None,
       
   194     tuple_factory=tuple,
       
   195     retain_collection_types=False,
       
   196 ):
    72     """
   197     """
    73     Return the ``attrs`` attribute values of *inst* as a tuple.
   198     Return the ``attrs`` attribute values of *inst* as a tuple.
    74 
   199 
    75     Optionally recurse into other ``attrs``-decorated classes.
   200     Optionally recurse into other ``attrs``-decorated classes.
    76 
   201 
    77     :param inst: Instance of an ``attrs``-decorated class.
   202     :param inst: Instance of an ``attrs``-decorated class.
    78     :param bool recurse: Recurse into classes that are also
   203     :param bool recurse: Recurse into classes that are also
    79         ``attrs``-decorated.
   204         ``attrs``-decorated.
    80     :param callable filter: A callable whose return code determines whether an
   205     :param callable filter: A callable whose return code determines whether an
    81         attribute or element is included (``True``) or dropped (``False``).  Is
   206         attribute or element is included (``True``) or dropped (``False``).  Is
    82         called with the :class:`attr.Attribute` as the first argument and the
   207         called with the `attrs.Attribute` as the first argument and the
    83         value as the second argument.
   208         value as the second argument.
    84     :param callable tuple_factory: A callable to produce tuples from.  For
   209     :param callable tuple_factory: A callable to produce tuples from.  For
    85         example, to produce lists instead of tuples.
   210         example, to produce lists instead of tuples.
    86     :param bool retain_collection_types: Do not convert to ``list``
   211     :param bool retain_collection_types: Do not convert to ``list``
    87         or ``dict`` when encountering an attribute which type is
   212         or ``dict`` when encountering an attribute which type is
   102         v = getattr(inst, a.name)
   227         v = getattr(inst, a.name)
   103         if filter is not None and not filter(a, v):
   228         if filter is not None and not filter(a, v):
   104             continue
   229             continue
   105         if recurse is True:
   230         if recurse is True:
   106             if has(v.__class__):
   231             if has(v.__class__):
   107                 rv.append(astuple(v, recurse=True, filter=filter,
   232                 rv.append(
   108                                   tuple_factory=tuple_factory,
   233                     astuple(
   109                                   retain_collection_types=retain))
   234                         v,
   110             elif isinstance(v, (tuple, list, set)):
   235                         recurse=True,
       
   236                         filter=filter,
       
   237                         tuple_factory=tuple_factory,
       
   238                         retain_collection_types=retain,
       
   239                     )
       
   240                 )
       
   241             elif isinstance(v, (tuple, list, set, frozenset)):
   111                 cf = v.__class__ if retain is True else list
   242                 cf = v.__class__ if retain is True else list
   112                 rv.append(cf([
   243                 rv.append(
   113                     astuple(j, recurse=True, filter=filter,
   244                     cf(
   114                             tuple_factory=tuple_factory,
   245                         [
   115                             retain_collection_types=retain)
   246                             astuple(
   116                     if has(j.__class__) else j
   247                                 j,
   117                     for j in v
   248                                 recurse=True,
   118                 ]))
   249                                 filter=filter,
       
   250                                 tuple_factory=tuple_factory,
       
   251                                 retain_collection_types=retain,
       
   252                             )
       
   253                             if has(j.__class__)
       
   254                             else j
       
   255                             for j in v
       
   256                         ]
       
   257                     )
       
   258                 )
   119             elif isinstance(v, dict):
   259             elif isinstance(v, dict):
   120                 df = v.__class__ if retain is True else dict
   260                 df = v.__class__ if retain is True else dict
   121                 rv.append(df(
   261                 rv.append(
       
   262                     df(
   122                         (
   263                         (
   123                             astuple(
   264                             astuple(
   124                                 kk,
   265                                 kk,
   125                                 tuple_factory=tuple_factory,
   266                                 tuple_factory=tuple_factory,
   126                                 retain_collection_types=retain
   267                                 retain_collection_types=retain,
   127                             ) if has(kk.__class__) else kk,
   268                             )
       
   269                             if has(kk.__class__)
       
   270                             else kk,
   128                             astuple(
   271                             astuple(
   129                                 vv,
   272                                 vv,
   130                                 tuple_factory=tuple_factory,
   273                                 tuple_factory=tuple_factory,
   131                                 retain_collection_types=retain
   274                                 retain_collection_types=retain,
   132                             ) if has(vv.__class__) else vv
   275                             )
       
   276                             if has(vv.__class__)
       
   277                             else vv,
   133                         )
   278                         )
   134                         for kk, vv in iteritems(v)))
   279                         for kk, vv in v.items()
       
   280                     )
       
   281                 )
   135             else:
   282             else:
   136                 rv.append(v)
   283                 rv.append(v)
   137         else:
   284         else:
   138             rv.append(v)
   285             rv.append(v)
       
   286 
   139     return rv if tuple_factory is list else tuple_factory(rv)
   287     return rv if tuple_factory is list else tuple_factory(rv)
   140 
   288 
   141 
   289 
   142 def has(cls):
   290 def has(cls):
   143     """
   291     """
   144     Check whether *cls* is a class with ``attrs`` attributes.
   292     Check whether *cls* is a class with ``attrs`` attributes.
   145 
   293 
   146     :param type cls: Class to introspect.
   294     :param type cls: Class to introspect.
   147     :raise TypeError: If *cls* is not a class.
   295     :raise TypeError: If *cls* is not a class.
   148 
   296 
   149     :rtype: :class:`bool`
   297     :rtype: bool
   150     """
   298     """
   151     return getattr(cls, "__attrs_attrs__", None) is not None
   299     return getattr(cls, "__attrs_attrs__", None) is not None
   152 
   300 
   153 
   301 
   154 def assoc(inst, **changes):
   302 def assoc(inst, **changes):
   164         be found on *cls*.
   312         be found on *cls*.
   165     :raise attr.exceptions.NotAnAttrsClassError: If *cls* is not an ``attrs``
   313     :raise attr.exceptions.NotAnAttrsClassError: If *cls* is not an ``attrs``
   166         class.
   314         class.
   167 
   315 
   168     ..  deprecated:: 17.1.0
   316     ..  deprecated:: 17.1.0
   169         Use :func:`evolve` instead.
   317         Use `attrs.evolve` instead if you can.
       
   318         This function will not be removed du to the slightly different approach
       
   319         compared to `attrs.evolve`.
   170     """
   320     """
   171     import warnings
   321     import warnings
   172     warnings.warn("assoc is deprecated and will be removed after 2018/01.",
   322 
   173                   DeprecationWarning)
   323     warnings.warn(
       
   324         "assoc is deprecated and will be removed after 2018/01.",
       
   325         DeprecationWarning,
       
   326         stacklevel=2,
       
   327     )
   174     new = copy.copy(inst)
   328     new = copy.copy(inst)
   175     attrs = fields(inst.__class__)
   329     attrs = fields(inst.__class__)
   176     for k, v in iteritems(changes):
   330     for k, v in changes.items():
   177         a = getattr(attrs, k, NOTHING)
   331         a = getattr(attrs, k, NOTHING)
   178         if a is NOTHING:
   332         if a is NOTHING:
   179             raise AttrsAttributeNotFoundError(
   333             raise AttrsAttributeNotFoundError(
   180                 "{k} is not an attrs attribute on {cl}."
   334                 "{k} is not an attrs attribute on {cl}.".format(
   181                 .format(k=k, cl=new.__class__)
   335                     k=k, cl=new.__class__
       
   336                 )
   182             )
   337             )
   183         _obj_setattr(new, k, v)
   338         _obj_setattr(new, k, v)
   184     return new
   339     return new
   185 
   340 
   186 
   341 
   207             continue
   362             continue
   208         attr_name = a.name  # To deal with private attributes.
   363         attr_name = a.name  # To deal with private attributes.
   209         init_name = attr_name if attr_name[0] != "_" else attr_name[1:]
   364         init_name = attr_name if attr_name[0] != "_" else attr_name[1:]
   210         if init_name not in changes:
   365         if init_name not in changes:
   211             changes[init_name] = getattr(inst, attr_name)
   366             changes[init_name] = getattr(inst, attr_name)
       
   367 
   212     return cls(**changes)
   368     return cls(**changes)
       
   369 
       
   370 
       
   371 def resolve_types(cls, globalns=None, localns=None, attribs=None):
       
   372     """
       
   373     Resolve any strings and forward annotations in type annotations.
       
   374 
       
   375     This is only required if you need concrete types in `Attribute`'s *type*
       
   376     field. In other words, you don't need to resolve your types if you only
       
   377     use them for static type checking.
       
   378 
       
   379     With no arguments, names will be looked up in the module in which the class
       
   380     was created. If this is not what you want, e.g. if the name only exists
       
   381     inside a method, you may pass *globalns* or *localns* to specify other
       
   382     dictionaries in which to look up these names. See the docs of
       
   383     `typing.get_type_hints` for more details.
       
   384 
       
   385     :param type cls: Class to resolve.
       
   386     :param Optional[dict] globalns: Dictionary containing global variables.
       
   387     :param Optional[dict] localns: Dictionary containing local variables.
       
   388     :param Optional[list] attribs: List of attribs for the given class.
       
   389         This is necessary when calling from inside a ``field_transformer``
       
   390         since *cls* is not an ``attrs`` class yet.
       
   391 
       
   392     :raise TypeError: If *cls* is not a class.
       
   393     :raise attr.exceptions.NotAnAttrsClassError: If *cls* is not an ``attrs``
       
   394         class and you didn't pass any attribs.
       
   395     :raise NameError: If types cannot be resolved because of missing variables.
       
   396 
       
   397     :returns: *cls* so you can use this function also as a class decorator.
       
   398         Please note that you have to apply it **after** `attrs.define`. That
       
   399         means the decorator has to come in the line **before** `attrs.define`.
       
   400 
       
   401     ..  versionadded:: 20.1.0
       
   402     ..  versionadded:: 21.1.0 *attribs*
       
   403 
       
   404     """
       
   405     # Since calling get_type_hints is expensive we cache whether we've
       
   406     # done it already.
       
   407     if getattr(cls, "__attrs_types_resolved__", None) != cls:
       
   408         import typing
       
   409 
       
   410         hints = typing.get_type_hints(cls, globalns=globalns, localns=localns)
       
   411         for field in fields(cls) if attribs is None else attribs:
       
   412             if field.name in hints:
       
   413                 # Since fields have been frozen we must work around it.
       
   414                 _obj_setattr(field, "type", hints[field.name])
       
   415         # We store the class we resolved so that subclasses know they haven't
       
   416         # been resolved.
       
   417         cls.__attrs_types_resolved__ = cls
       
   418 
       
   419     # Return the class so you can use it as a decorator too.
       
   420     return cls