123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422 |
- # SPDX-License-Identifier: MIT
- from __future__ import absolute_import, division, print_function
- import copy
- from ._compat import iteritems
- from ._make import NOTHING, _obj_setattr, fields
- from .exceptions import AttrsAttributeNotFoundError
- def asdict(
- inst,
- recurse=True,
- filter=None,
- dict_factory=dict,
- retain_collection_types=False,
- value_serializer=None,
- ):
- """
- Return the ``attrs`` attribute values of *inst* as a dict.
- Optionally recurse into other ``attrs``-decorated classes.
- :param inst: Instance of an ``attrs``-decorated class.
- :param bool recurse: Recurse into classes that are also
- ``attrs``-decorated.
- :param callable filter: A callable whose return code determines whether an
- attribute or element is included (``True``) or dropped (``False``). Is
- called with the `attrs.Attribute` as the first argument and the
- value as the second argument.
- :param callable dict_factory: A callable to produce dictionaries from. For
- example, to produce ordered dictionaries instead of normal Python
- dictionaries, pass in ``collections.OrderedDict``.
- :param bool retain_collection_types: Do not convert to ``list`` when
- encountering an attribute whose type is ``tuple`` or ``set``. Only
- meaningful if ``recurse`` is ``True``.
- :param Optional[callable] value_serializer: A hook that is called for every
- attribute or dict key/value. It receives the current instance, field
- and value and must return the (updated) value. The hook is run *after*
- the optional *filter* has been applied.
- :rtype: return type of *dict_factory*
- :raise attr.exceptions.NotAnAttrsClassError: If *cls* is not an ``attrs``
- class.
- .. versionadded:: 16.0.0 *dict_factory*
- .. versionadded:: 16.1.0 *retain_collection_types*
- .. versionadded:: 20.3.0 *value_serializer*
- .. versionadded:: 21.3.0 If a dict has a collection for a key, it is
- serialized as a tuple.
- """
- attrs = fields(inst.__class__)
- rv = dict_factory()
- for a in attrs:
- v = getattr(inst, a.name)
- if filter is not None and not filter(a, v):
- continue
- if value_serializer is not None:
- v = value_serializer(inst, a, v)
- if recurse is True:
- if has(v.__class__):
- rv[a.name] = asdict(
- v,
- recurse=True,
- filter=filter,
- dict_factory=dict_factory,
- retain_collection_types=retain_collection_types,
- value_serializer=value_serializer,
- )
- elif isinstance(v, (tuple, list, set, frozenset)):
- cf = v.__class__ if retain_collection_types is True else list
- rv[a.name] = cf(
- [
- _asdict_anything(
- i,
- is_key=False,
- filter=filter,
- dict_factory=dict_factory,
- retain_collection_types=retain_collection_types,
- value_serializer=value_serializer,
- )
- for i in v
- ]
- )
- elif isinstance(v, dict):
- df = dict_factory
- rv[a.name] = df(
- (
- _asdict_anything(
- kk,
- is_key=True,
- filter=filter,
- dict_factory=df,
- retain_collection_types=retain_collection_types,
- value_serializer=value_serializer,
- ),
- _asdict_anything(
- vv,
- is_key=False,
- filter=filter,
- dict_factory=df,
- retain_collection_types=retain_collection_types,
- value_serializer=value_serializer,
- ),
- )
- for kk, vv in iteritems(v)
- )
- else:
- rv[a.name] = v
- else:
- rv[a.name] = v
- return rv
- def _asdict_anything(
- val,
- is_key,
- filter,
- dict_factory,
- retain_collection_types,
- value_serializer,
- ):
- """
- ``asdict`` only works on attrs instances, this works on anything.
- """
- if getattr(val.__class__, "__attrs_attrs__", None) is not None:
- # Attrs class.
- rv = asdict(
- val,
- recurse=True,
- filter=filter,
- dict_factory=dict_factory,
- retain_collection_types=retain_collection_types,
- value_serializer=value_serializer,
- )
- elif isinstance(val, (tuple, list, set, frozenset)):
- if retain_collection_types is True:
- cf = val.__class__
- elif is_key:
- cf = tuple
- else:
- cf = list
- rv = cf(
- [
- _asdict_anything(
- i,
- is_key=False,
- filter=filter,
- dict_factory=dict_factory,
- retain_collection_types=retain_collection_types,
- value_serializer=value_serializer,
- )
- for i in val
- ]
- )
- elif isinstance(val, dict):
- df = dict_factory
- rv = df(
- (
- _asdict_anything(
- kk,
- is_key=True,
- filter=filter,
- dict_factory=df,
- retain_collection_types=retain_collection_types,
- value_serializer=value_serializer,
- ),
- _asdict_anything(
- vv,
- is_key=False,
- filter=filter,
- dict_factory=df,
- retain_collection_types=retain_collection_types,
- value_serializer=value_serializer,
- ),
- )
- for kk, vv in iteritems(val)
- )
- else:
- rv = val
- if value_serializer is not None:
- rv = value_serializer(None, None, rv)
- return rv
- def astuple(
- inst,
- recurse=True,
- filter=None,
- tuple_factory=tuple,
- retain_collection_types=False,
- ):
- """
- Return the ``attrs`` attribute values of *inst* as a tuple.
- Optionally recurse into other ``attrs``-decorated classes.
- :param inst: Instance of an ``attrs``-decorated class.
- :param bool recurse: Recurse into classes that are also
- ``attrs``-decorated.
- :param callable filter: A callable whose return code determines whether an
- attribute or element is included (``True``) or dropped (``False``). Is
- called with the `attrs.Attribute` as the first argument and the
- value as the second argument.
- :param callable tuple_factory: A callable to produce tuples from. For
- example, to produce lists instead of tuples.
- :param bool retain_collection_types: Do not convert to ``list``
- or ``dict`` when encountering an attribute which type is
- ``tuple``, ``dict`` or ``set``. Only meaningful if ``recurse`` is
- ``True``.
- :rtype: return type of *tuple_factory*
- :raise attr.exceptions.NotAnAttrsClassError: If *cls* is not an ``attrs``
- class.
- .. versionadded:: 16.2.0
- """
- attrs = fields(inst.__class__)
- rv = []
- retain = retain_collection_types # Very long. :/
- for a in attrs:
- v = getattr(inst, a.name)
- if filter is not None and not filter(a, v):
- continue
- if recurse is True:
- if has(v.__class__):
- rv.append(
- astuple(
- v,
- recurse=True,
- filter=filter,
- tuple_factory=tuple_factory,
- retain_collection_types=retain,
- )
- )
- elif isinstance(v, (tuple, list, set, frozenset)):
- cf = v.__class__ if retain is True else list
- rv.append(
- cf(
- [
- astuple(
- j,
- recurse=True,
- filter=filter,
- tuple_factory=tuple_factory,
- retain_collection_types=retain,
- )
- if has(j.__class__)
- else j
- for j in v
- ]
- )
- )
- elif isinstance(v, dict):
- df = v.__class__ if retain is True else dict
- rv.append(
- df(
- (
- astuple(
- kk,
- tuple_factory=tuple_factory,
- retain_collection_types=retain,
- )
- if has(kk.__class__)
- else kk,
- astuple(
- vv,
- tuple_factory=tuple_factory,
- retain_collection_types=retain,
- )
- if has(vv.__class__)
- else vv,
- )
- for kk, vv in iteritems(v)
- )
- )
- else:
- rv.append(v)
- else:
- rv.append(v)
- return rv if tuple_factory is list else tuple_factory(rv)
- def has(cls):
- """
- Check whether *cls* is a class with ``attrs`` attributes.
- :param type cls: Class to introspect.
- :raise TypeError: If *cls* is not a class.
- :rtype: bool
- """
- return getattr(cls, "__attrs_attrs__", None) is not None
- def assoc(inst, **changes):
- """
- Copy *inst* and apply *changes*.
- :param inst: Instance of a class with ``attrs`` attributes.
- :param changes: Keyword changes in the new copy.
- :return: A copy of inst with *changes* incorporated.
- :raise attr.exceptions.AttrsAttributeNotFoundError: If *attr_name* couldn't
- be found on *cls*.
- :raise attr.exceptions.NotAnAttrsClassError: If *cls* is not an ``attrs``
- class.
- .. deprecated:: 17.1.0
- Use `attrs.evolve` instead if you can.
- This function will not be removed du to the slightly different approach
- compared to `attrs.evolve`.
- """
- import warnings
- warnings.warn(
- "assoc is deprecated and will be removed after 2018/01.",
- DeprecationWarning,
- stacklevel=2,
- )
- new = copy.copy(inst)
- attrs = fields(inst.__class__)
- for k, v in iteritems(changes):
- a = getattr(attrs, k, NOTHING)
- if a is NOTHING:
- raise AttrsAttributeNotFoundError(
- "{k} is not an attrs attribute on {cl}.".format(
- k=k, cl=new.__class__
- )
- )
- _obj_setattr(new, k, v)
- return new
- def evolve(inst, **changes):
- """
- Create a new instance, based on *inst* with *changes* applied.
- :param inst: Instance of a class with ``attrs`` attributes.
- :param changes: Keyword changes in the new copy.
- :return: A copy of inst with *changes* incorporated.
- :raise TypeError: If *attr_name* couldn't be found in the class
- ``__init__``.
- :raise attr.exceptions.NotAnAttrsClassError: If *cls* is not an ``attrs``
- class.
- .. versionadded:: 17.1.0
- """
- cls = inst.__class__
- attrs = fields(cls)
- for a in attrs:
- if not a.init:
- continue
- attr_name = a.name # To deal with private attributes.
- init_name = attr_name if attr_name[0] != "_" else attr_name[1:]
- if init_name not in changes:
- changes[init_name] = getattr(inst, attr_name)
- return cls(**changes)
- def resolve_types(cls, globalns=None, localns=None, attribs=None):
- """
- Resolve any strings and forward annotations in type annotations.
- This is only required if you need concrete types in `Attribute`'s *type*
- field. In other words, you don't need to resolve your types if you only
- use them for static type checking.
- With no arguments, names will be looked up in the module in which the class
- was created. If this is not what you want, e.g. if the name only exists
- inside a method, you may pass *globalns* or *localns* to specify other
- dictionaries in which to look up these names. See the docs of
- `typing.get_type_hints` for more details.
- :param type cls: Class to resolve.
- :param Optional[dict] globalns: Dictionary containing global variables.
- :param Optional[dict] localns: Dictionary containing local variables.
- :param Optional[list] attribs: List of attribs for the given class.
- This is necessary when calling from inside a ``field_transformer``
- since *cls* is not an ``attrs`` class yet.
- :raise TypeError: If *cls* is not a class.
- :raise attr.exceptions.NotAnAttrsClassError: If *cls* is not an ``attrs``
- class and you didn't pass any attribs.
- :raise NameError: If types cannot be resolved because of missing variables.
- :returns: *cls* so you can use this function also as a class decorator.
- Please note that you have to apply it **after** `attrs.define`. That
- means the decorator has to come in the line **before** `attrs.define`.
- .. versionadded:: 20.1.0
- .. versionadded:: 21.1.0 *attribs*
- """
- # Since calling get_type_hints is expensive we cache whether we've
- # done it already.
- if getattr(cls, "__attrs_types_resolved__", None) != cls:
- import typing
- hints = typing.get_type_hints(cls, globalns=globalns, localns=localns)
- for field in fields(cls) if attribs is None else attribs:
- if field.name in hints:
- # Since fields have been frozen we must work around it.
- _obj_setattr(field, "type", hints[field.name])
- # We store the class we resolved so that subclasses know they haven't
- # been resolved.
- cls.__attrs_types_resolved__ = cls
- # Return the class so you can use it as a decorator too.
- return cls
|