--- a/mercurial/thirdparty/attr/_funcs.py Mon Nov 21 16:18:28 2022 -0500
+++ b/mercurial/thirdparty/attr/_funcs.py Mon Nov 21 15:04:42 2022 -0500
@@ -1,14 +1,20 @@
-from __future__ import absolute_import, division, print_function
+# SPDX-License-Identifier: MIT
+
import copy
-from ._compat import iteritems
-from ._make import NOTHING, fields, _obj_setattr
+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):
+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.
@@ -17,9 +23,9 @@
: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 deteremines whether an
+ :param callable filter: A callable whose return code determines whether an
attribute or element is included (``True``) or dropped (``False``). Is
- called with the :class:`attr.Attribute` as the first argument and the
+ 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
@@ -27,6 +33,10 @@
: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*
@@ -35,6 +45,9 @@
.. 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()
@@ -42,24 +55,58 @@
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)
- elif isinstance(v, (tuple, list, set)):
+ 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(i, recurse=True, filter=filter,
- dict_factory=dict_factory)
- if has(i.__class__) else i
- for i in v
- ])
+ 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(kk, dict_factory=df) if has(kk.__class__) else kk,
- asdict(vv, dict_factory=df) if has(vv.__class__) else vv)
- for kk, vv in iteritems(v))
+ 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 v.items()
+ )
else:
rv[a.name] = v
else:
@@ -67,8 +114,86 @@
return rv
-def astuple(inst, recurse=True, filter=None, tuple_factory=tuple,
- retain_collection_types=False):
+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 val.items()
+ )
+ 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.
@@ -79,7 +204,7 @@
``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 :class:`attr.Attribute` as the first argument and the
+ 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.
@@ -104,38 +229,61 @@
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)):
+ 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
- ]))
+ 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(
+ rv.append(
+ df(
(
astuple(
kk,
tuple_factory=tuple_factory,
- retain_collection_types=retain
- ) if has(kk.__class__) else kk,
+ 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
+ retain_collection_types=retain,
+ )
+ if has(vv.__class__)
+ else vv,
)
- for kk, vv in iteritems(v)))
+ for kk, vv in v.items()
+ )
+ )
else:
rv.append(v)
else:
rv.append(v)
+
return rv if tuple_factory is list else tuple_factory(rv)
@@ -146,7 +294,7 @@
:param type cls: Class to introspect.
:raise TypeError: If *cls* is not a class.
- :rtype: :class:`bool`
+ :rtype: bool
"""
return getattr(cls, "__attrs_attrs__", None) is not None
@@ -166,19 +314,26 @@
class.
.. deprecated:: 17.1.0
- Use :func:`evolve` instead.
+ 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)
+
+ 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):
+ for k, v in changes.items():
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__)
+ "{k} is not an attrs attribute on {cl}.".format(
+ k=k, cl=new.__class__
+ )
)
_obj_setattr(new, k, v)
return new
@@ -209,4 +364,57 @@
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