Browse Source

Update attr 20.2.0 (4f74fba) → 20.3.0 (f3762ba).

tags/release_0.25.1
JackDandy 5 years ago
parent
commit
fcc24816cd
  1. 1
      CHANGES.md
  2. 2
      lib/attr/__init__.py
  3. 10
      lib/attr/__init__.pyi
  4. 4
      lib/attr/_compat.py
  5. 76
      lib/attr/_funcs.py
  6. 148
      lib/attr/_make.py
  7. 2
      lib/attr/_next_gen.py

1
CHANGES.md

@ -1,6 +1,7 @@
### 0.24.0 (202x-xx-xx xx:xx:00 UTC) ### 0.24.0 (202x-xx-xx xx:xx:00 UTC)
* Change abbreviate long titles under menu tab * Change abbreviate long titles under menu tab
* Update attr 20.2.0 (4f74fba) to 20.3.0 (f3762ba)
* Update Requests library 2.24.0 (2f70990) to 2.25.0 (03957eb) * Update Requests library 2.24.0 (2f70990) to 2.25.0 (03957eb)
* Update urllib3 1.25.11 (00f1769) to 1.26.1 (7675532) * Update urllib3 1.25.11 (00f1769) to 1.26.1 (7675532)

2
lib/attr/__init__.py

@ -21,7 +21,7 @@ from ._make import (
from ._version_info import VersionInfo from ._version_info import VersionInfo
__version__ = "20.2.0" __version__ = "20.3.0"
__version_info__ = VersionInfo._from_version_string(__version__) __version_info__ = VersionInfo._from_version_string(__version__)
__title__ = "attrs" __title__ = "attrs"

10
lib/attr/__init__.pyi

@ -46,6 +46,7 @@ _OnSetAttrType = Callable[[Any, Attribute[Any], Any], Any]
_OnSetAttrArgType = Union[ _OnSetAttrArgType = Union[
_OnSetAttrType, List[_OnSetAttrType], setters._NoOpType _OnSetAttrType, List[_OnSetAttrType], setters._NoOpType
] ]
_FieldTransformer = Callable[[type, List[Attribute]], List[Attribute]]
# FIXME: in reality, if multiple validators are passed they must be in a list # FIXME: in reality, if multiple validators are passed they must be in a list
# or tuple, but those are invariant and so would prevent subtypes of # or tuple, but those are invariant and so would prevent subtypes of
# _ValidatorType from working when passed in a list or tuple. # _ValidatorType from working when passed in a list or tuple.
@ -272,8 +273,10 @@ def attrs(
eq: Optional[bool] = ..., eq: Optional[bool] = ...,
order: Optional[bool] = ..., order: Optional[bool] = ...,
auto_detect: bool = ..., auto_detect: bool = ...,
collect_by_mro: bool = ...,
getstate_setstate: Optional[bool] = ..., getstate_setstate: Optional[bool] = ...,
on_setattr: Optional[_OnSetAttrArgType] = ..., on_setattr: Optional[_OnSetAttrArgType] = ...,
field_transformer: Optional[_FieldTransformer] = ...,
) -> _C: ... ) -> _C: ...
@overload @overload
def attrs( def attrs(
@ -295,8 +298,10 @@ def attrs(
eq: Optional[bool] = ..., eq: Optional[bool] = ...,
order: Optional[bool] = ..., order: Optional[bool] = ...,
auto_detect: bool = ..., auto_detect: bool = ...,
collect_by_mro: bool = ...,
getstate_setstate: Optional[bool] = ..., getstate_setstate: Optional[bool] = ...,
on_setattr: Optional[_OnSetAttrArgType] = ..., on_setattr: Optional[_OnSetAttrArgType] = ...,
field_transformer: Optional[_FieldTransformer] = ...,
) -> Callable[[_C], _C]: ... ) -> Callable[[_C], _C]: ...
@overload @overload
def define( def define(
@ -319,6 +324,7 @@ def define(
auto_detect: bool = ..., auto_detect: bool = ...,
getstate_setstate: Optional[bool] = ..., getstate_setstate: Optional[bool] = ...,
on_setattr: Optional[_OnSetAttrArgType] = ..., on_setattr: Optional[_OnSetAttrArgType] = ...,
field_transformer: Optional[_FieldTransformer] = ...,
) -> _C: ... ) -> _C: ...
@overload @overload
def define( def define(
@ -341,6 +347,7 @@ def define(
auto_detect: bool = ..., auto_detect: bool = ...,
getstate_setstate: Optional[bool] = ..., getstate_setstate: Optional[bool] = ...,
on_setattr: Optional[_OnSetAttrArgType] = ..., on_setattr: Optional[_OnSetAttrArgType] = ...,
field_transformer: Optional[_FieldTransformer] = ...,
) -> Callable[[_C], _C]: ... ) -> Callable[[_C], _C]: ...
mutable = define mutable = define
@ -381,7 +388,9 @@ def make_class(
auto_exc: bool = ..., auto_exc: bool = ...,
eq: Optional[bool] = ..., eq: Optional[bool] = ...,
order: Optional[bool] = ..., order: Optional[bool] = ...,
collect_by_mro: bool = ...,
on_setattr: Optional[_OnSetAttrArgType] = ..., on_setattr: Optional[_OnSetAttrArgType] = ...,
field_transformer: Optional[_FieldTransformer] = ...,
) -> type: ... ) -> type: ...
# _funcs -- # _funcs --
@ -397,6 +406,7 @@ def asdict(
filter: Optional[_FilterType[Any]] = ..., filter: Optional[_FilterType[Any]] = ...,
dict_factory: Type[Mapping[Any, Any]] = ..., dict_factory: Type[Mapping[Any, Any]] = ...,
retain_collection_types: bool = ..., retain_collection_types: bool = ...,
value_serializer: Optional[Callable[[type, Attribute, Any], Any]] = ...,
) -> Dict[str, Any]: ... ) -> Dict[str, Any]: ...
# TODO: add support for returning NamedTuple from the mypy plugin # TODO: add support for returning NamedTuple from the mypy plugin

4
lib/attr/_compat.py

@ -91,7 +91,7 @@ if PY2:
res.data.update(d) # We blocked update, so we have to do it like this. res.data.update(d) # We blocked update, so we have to do it like this.
return res return res
def just_warn(*args, **kw): # pragma: nocover def just_warn(*args, **kw): # pragma: no cover
""" """
We only warn on Python 3 because we are not aware of any concrete We only warn on Python 3 because we are not aware of any concrete
consequences of not setting the cell on Python 2. consequences of not setting the cell on Python 2.
@ -132,7 +132,7 @@ def make_set_closure_cell():
""" """
# pypy makes this easy. (It also supports the logic below, but # pypy makes this easy. (It also supports the logic below, but
# why not do the easy/fast thing?) # why not do the easy/fast thing?)
if PYPY: # pragma: no cover if PYPY:
def set_closure_cell(cell, value): def set_closure_cell(cell, value):
cell.__setstate__((value,)) cell.__setstate__((value,))

76
lib/attr/_funcs.py

@ -13,6 +13,7 @@ def asdict(
filter=None, filter=None,
dict_factory=dict, dict_factory=dict,
retain_collection_types=False, retain_collection_types=False,
value_serializer=None,
): ):
""" """
Return the ``attrs`` attribute values of *inst* as a dict. Return the ``attrs`` attribute values of *inst* as a dict.
@ -32,6 +33,10 @@ def asdict(
:param bool retain_collection_types: Do not convert to ``list`` when :param bool retain_collection_types: Do not convert to ``list`` when
encountering an attribute whose type is ``tuple`` or ``set``. Only encountering an attribute whose type is ``tuple`` or ``set``. Only
meaningful if ``recurse`` is ``True``. 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* :rtype: return type of *dict_factory*
@ -40,6 +45,7 @@ def asdict(
.. versionadded:: 16.0.0 *dict_factory* .. versionadded:: 16.0.0 *dict_factory*
.. versionadded:: 16.1.0 *retain_collection_types* .. versionadded:: 16.1.0 *retain_collection_types*
.. versionadded:: 20.3.0 *value_serializer*
""" """
attrs = fields(inst.__class__) attrs = fields(inst.__class__)
rv = dict_factory() rv = dict_factory()
@ -47,17 +53,30 @@ def asdict(
v = getattr(inst, a.name) v = getattr(inst, a.name)
if filter is not None and not filter(a, v): if filter is not None and not filter(a, v):
continue continue
if value_serializer is not None:
v = value_serializer(inst, a, v)
if recurse is True: if recurse is True:
if has(v.__class__): if has(v.__class__):
rv[a.name] = asdict( rv[a.name] = asdict(
v, True, filter, dict_factory, retain_collection_types v,
True,
filter,
dict_factory,
retain_collection_types,
value_serializer,
) )
elif isinstance(v, (tuple, list, set)): elif isinstance(v, (tuple, list, set, frozenset)):
cf = v.__class__ if retain_collection_types is True else list cf = v.__class__ if retain_collection_types is True else list
rv[a.name] = cf( rv[a.name] = cf(
[ [
_asdict_anything( _asdict_anything(
i, filter, dict_factory, retain_collection_types i,
filter,
dict_factory,
retain_collection_types,
value_serializer,
) )
for i in v for i in v
] ]
@ -67,10 +86,18 @@ def asdict(
rv[a.name] = df( rv[a.name] = df(
( (
_asdict_anything( _asdict_anything(
kk, filter, df, retain_collection_types kk,
filter,
df,
retain_collection_types,
value_serializer,
), ),
_asdict_anything( _asdict_anything(
vv, filter, df, retain_collection_types vv,
filter,
df,
retain_collection_types,
value_serializer,
), ),
) )
for kk, vv in iteritems(v) for kk, vv in iteritems(v)
@ -82,19 +109,36 @@ def asdict(
return rv return rv
def _asdict_anything(val, filter, dict_factory, retain_collection_types): def _asdict_anything(
val,
filter,
dict_factory,
retain_collection_types,
value_serializer,
):
""" """
``asdict`` only works on attrs instances, this works on anything. ``asdict`` only works on attrs instances, this works on anything.
""" """
if getattr(val.__class__, "__attrs_attrs__", None) is not None: if getattr(val.__class__, "__attrs_attrs__", None) is not None:
# Attrs class. # Attrs class.
rv = asdict(val, True, filter, dict_factory, retain_collection_types) rv = asdict(
elif isinstance(val, (tuple, list, set)): val,
True,
filter,
dict_factory,
retain_collection_types,
value_serializer,
)
elif isinstance(val, (tuple, list, set, frozenset)):
cf = val.__class__ if retain_collection_types is True else list cf = val.__class__ if retain_collection_types is True else list
rv = cf( rv = cf(
[ [
_asdict_anything( _asdict_anything(
i, filter, dict_factory, retain_collection_types i,
filter,
dict_factory,
retain_collection_types,
value_serializer,
) )
for i in val for i in val
] ]
@ -103,13 +147,20 @@ def _asdict_anything(val, filter, dict_factory, retain_collection_types):
df = dict_factory df = dict_factory
rv = df( rv = df(
( (
_asdict_anything(kk, filter, df, retain_collection_types), _asdict_anything(
_asdict_anything(vv, filter, df, retain_collection_types), kk, filter, df, retain_collection_types, value_serializer
),
_asdict_anything(
vv, filter, df, retain_collection_types, value_serializer
),
) )
for kk, vv in iteritems(val) for kk, vv in iteritems(val)
) )
else: else:
rv = val rv = val
if value_serializer is not None:
rv = value_serializer(None, None, rv)
return rv return rv
@ -164,7 +215,7 @@ def astuple(
retain_collection_types=retain, retain_collection_types=retain,
) )
) )
elif isinstance(v, (tuple, list, set)): elif isinstance(v, (tuple, list, set, frozenset)):
cf = v.__class__ if retain is True else list cf = v.__class__ if retain is True else list
rv.append( rv.append(
cf( cf(
@ -209,6 +260,7 @@ def astuple(
rv.append(v) rv.append(v)
else: else:
rv.append(v) rv.append(v)
return rv if tuple_factory is list else tuple_factory(rv) return rv if tuple_factory is list else tuple_factory(rv)

148
lib/attr/_make.py

@ -12,6 +12,7 @@ from operator import itemgetter
from . import _config, setters from . import _config, setters
from ._compat import ( from ._compat import (
PY2, PY2,
PYPY,
isclass, isclass,
iteritems, iteritems,
metadata_proxy, metadata_proxy,
@ -223,6 +224,7 @@ def attrib(
.. deprecated:: 19.2.0 *cmp* Removal on or after 2021-06-01. .. deprecated:: 19.2.0 *cmp* Removal on or after 2021-06-01.
.. versionadded:: 19.2.0 *eq* and *order* .. versionadded:: 19.2.0 *eq* and *order*
.. versionadded:: 20.1.0 *on_setattr* .. versionadded:: 20.1.0 *on_setattr*
.. versionchanged:: 20.3.0 *kw_only* backported to Python 2
""" """
eq, order = _determine_eq_order(cmp, eq, order, True) eq, order = _determine_eq_order(cmp, eq, order, True)
@ -373,7 +375,7 @@ def _collect_base_attrs(cls, taken_attr_names):
if a.inherited or a.name in taken_attr_names: if a.inherited or a.name in taken_attr_names:
continue continue
a = a._assoc(inherited=True) a = a.evolve(inherited=True)
base_attrs.append(a) base_attrs.append(a)
base_attr_map[a.name] = base_cls base_attr_map[a.name] = base_cls
@ -411,7 +413,7 @@ def _collect_base_attrs_broken(cls, taken_attr_names):
if a.name in taken_attr_names: if a.name in taken_attr_names:
continue continue
a = a._assoc(inherited=True) a = a.evolve(inherited=True)
taken_attr_names.add(a.name) taken_attr_names.add(a.name)
base_attrs.append(a) base_attrs.append(a)
base_attr_map[a.name] = base_cls base_attr_map[a.name] = base_cls
@ -419,7 +421,9 @@ def _collect_base_attrs_broken(cls, taken_attr_names):
return base_attrs, base_attr_map return base_attrs, base_attr_map
def _transform_attrs(cls, these, auto_attribs, kw_only, collect_by_mro): def _transform_attrs(
cls, these, auto_attribs, kw_only, collect_by_mro, field_transformer
):
""" """
Transform all `_CountingAttr`s on a class into `Attribute`s. Transform all `_CountingAttr`s on a class into `Attribute`s.
@ -451,6 +455,7 @@ def _transform_attrs(cls, these, auto_attribs, kw_only, collect_by_mro):
continue continue
annot_names.add(attr_name) annot_names.add(attr_name)
a = cd.get(attr_name, NOTHING) a = cd.get(attr_name, NOTHING)
if not isinstance(a, _CountingAttr): if not isinstance(a, _CountingAttr):
if a is NOTHING: if a is NOTHING:
a = attrib() a = attrib()
@ -498,8 +503,8 @@ def _transform_attrs(cls, these, auto_attribs, kw_only, collect_by_mro):
AttrsClass = _make_attr_tuple_class(cls.__name__, attr_names) AttrsClass = _make_attr_tuple_class(cls.__name__, attr_names)
if kw_only: if kw_only:
own_attrs = [a._assoc(kw_only=True) for a in own_attrs] own_attrs = [a.evolve(kw_only=True) for a in own_attrs]
base_attrs = [a._assoc(kw_only=True) for a in base_attrs] base_attrs = [a.evolve(kw_only=True) for a in base_attrs]
attrs = AttrsClass(base_attrs + own_attrs) attrs = AttrsClass(base_attrs + own_attrs)
@ -518,10 +523,30 @@ def _transform_attrs(cls, these, auto_attribs, kw_only, collect_by_mro):
if had_default is False and a.default is not NOTHING: if had_default is False and a.default is not NOTHING:
had_default = True had_default = True
if field_transformer is not None:
attrs = field_transformer(cls, attrs)
return _Attributes((attrs, base_attrs, base_attr_map)) return _Attributes((attrs, base_attrs, base_attr_map))
def _frozen_setattrs(self, name, value): if PYPY:
def _frozen_setattrs(self, name, value):
"""
Attached to frozen classes as __setattr__.
"""
if isinstance(self, BaseException) and name in (
"__cause__",
"__context__",
):
BaseException.__setattr__(self, name, value)
return
raise FrozenInstanceError()
else:
def _frozen_setattrs(self, name, value):
""" """
Attached to frozen classes as __setattr__. Attached to frozen classes as __setattr__.
""" """
@ -574,9 +599,15 @@ class _ClassBuilder(object):
collect_by_mro, collect_by_mro,
on_setattr, on_setattr,
has_custom_setattr, has_custom_setattr,
field_transformer,
): ):
attrs, base_attrs, base_map = _transform_attrs( attrs, base_attrs, base_map = _transform_attrs(
cls, these, auto_attribs, kw_only, collect_by_mro cls,
these,
auto_attribs,
kw_only,
collect_by_mro,
field_transformer,
) )
self._cls = cls self._cls = cls
@ -1001,6 +1032,7 @@ def attrs(
collect_by_mro=False, collect_by_mro=False,
getstate_setstate=None, getstate_setstate=None,
on_setattr=None, on_setattr=None,
field_transformer=None,
): ):
r""" r"""
A class decorator that adds `dunder A class decorator that adds `dunder
@ -1093,12 +1125,14 @@ def attrs(
argument name. If a ``__attrs_post_init__`` method exists on the argument name. If a ``__attrs_post_init__`` method exists on the
class, it will be called after the class is fully initialized. class, it will be called after the class is fully initialized.
:param bool slots: Create a `slotted class <slotted classes>` that's more :param bool slots: Create a `slotted class <slotted classes>` that's more
memory-efficient. memory-efficient. Slotted classes are generally superior to the default
dict classes, but have some gotchas you should know about, so we
encourage you to read the `glossary entry <slotted classes>`.
:param bool frozen: Make instances immutable after initialization. If :param bool frozen: Make instances immutable after initialization. If
someone attempts to modify a frozen instance, someone attempts to modify a frozen instance,
`attr.exceptions.FrozenInstanceError` is raised. `attr.exceptions.FrozenInstanceError` is raised.
Please note: .. note::
1. This is achieved by installing a custom ``__setattr__`` method 1. This is achieved by installing a custom ``__setattr__`` method
on your class, so you can't implement your own. on your class, so you can't implement your own.
@ -1184,7 +1218,7 @@ def attrs(
:param on_setattr: A callable that is run whenever the user attempts to set :param on_setattr: A callable that is run whenever the user attempts to set
an attribute (either by assignment like ``i.x = 42`` or by using an attribute (either by assignment like ``i.x = 42`` or by using
`setattr` like ``setattr(i, "x", 42)``). It receives the same argument `setattr` like ``setattr(i, "x", 42)``). It receives the same arguments
as validators: the instance, the attribute that is being modified, and as validators: the instance, the attribute that is being modified, and
the new value. the new value.
@ -1194,6 +1228,11 @@ def attrs(
If a list of callables is passed, they're automatically wrapped in an If a list of callables is passed, they're automatically wrapped in an
`attr.setters.pipe`. `attr.setters.pipe`.
:param Optional[callable] field_transformer:
A function that is called with the original class object and all
fields right before ``attrs`` finalizes the class. You can use
this, e.g., to automatically add converters or validators to
fields based on their types. See `transform-fields` for more details.
.. versionadded:: 16.0.0 *slots* .. versionadded:: 16.0.0 *slots*
.. versionadded:: 16.1.0 *frozen* .. versionadded:: 16.1.0 *frozen*
@ -1223,6 +1262,7 @@ def attrs(
.. versionadded:: 20.1.0 *collect_by_mro* .. versionadded:: 20.1.0 *collect_by_mro*
.. versionadded:: 20.1.0 *getstate_setstate* .. versionadded:: 20.1.0 *getstate_setstate*
.. versionadded:: 20.1.0 *on_setattr* .. versionadded:: 20.1.0 *on_setattr*
.. versionadded:: 20.3.0 *field_transformer*
""" """
if auto_detect and PY2: if auto_detect and PY2:
raise PythonTooOldError( raise PythonTooOldError(
@ -1269,6 +1309,7 @@ def attrs(
collect_by_mro, collect_by_mro,
on_setattr, on_setattr,
has_own_setattr, has_own_setattr,
field_transformer,
) )
if _determine_whether_to_implement( if _determine_whether_to_implement(
cls, repr, auto_detect, ("__repr__",) cls, repr, auto_detect, ("__repr__",)
@ -1903,6 +1944,63 @@ def _assign_with_converter(attr_name, value_var, has_on_setattr):
) )
if PY2:
def _unpack_kw_only_py2(attr_name, default=None):
"""
Unpack *attr_name* from _kw_only dict.
"""
if default is not None:
arg_default = ", %s" % default
else:
arg_default = ""
return "%s = _kw_only.pop('%s'%s)" % (
attr_name,
attr_name,
arg_default,
)
def _unpack_kw_only_lines_py2(kw_only_args):
"""
Unpack all *kw_only_args* from _kw_only dict and handle errors.
Given a list of strings "{attr_name}" and "{attr_name}={default}"
generates list of lines of code that pop attrs from _kw_only dict and
raise TypeError similar to builtin if required attr is missing or
extra key is passed.
>>> print("\n".join(_unpack_kw_only_lines_py2(["a", "b=42"])))
try:
a = _kw_only.pop('a')
b = _kw_only.pop('b', 42)
except KeyError as _key_error:
raise TypeError(
...
if _kw_only:
raise TypeError(
...
"""
lines = ["try:"]
lines.extend(
" " + _unpack_kw_only_py2(*arg.split("="))
for arg in kw_only_args
)
lines += """\
except KeyError as _key_error:
raise TypeError(
'__init__() missing required keyword-only argument: %s' % _key_error
)
if _kw_only:
raise TypeError(
'__init__() got an unexpected keyword argument %r'
% next(iter(_kw_only))
)
""".split(
"\n"
)
return lines
def _attrs_to_init_script( def _attrs_to_init_script(
attrs, attrs,
frozen, frozen,
@ -2157,13 +2255,13 @@ def _attrs_to_init_script(
args = ", ".join(args) args = ", ".join(args)
if kw_only_args: if kw_only_args:
if PY2: if PY2:
raise PythonTooOldError( lines = _unpack_kw_only_lines_py2(kw_only_args) + lines
"Keyword-only arguments only work on Python 3 and later."
)
args += "{leading_comma}*, {kw_only_args}".format( args += "%s**_kw_only" % (", " if args else "",) # leading comma
leading_comma=", " if args else "", else:
kw_only_args=", ".join(kw_only_args), args += "%s*, %s" % (
", " if args else "", # leading comma
", ".join(kw_only_args), # kw_only args
) )
return ( return (
"""\ """\
@ -2181,6 +2279,13 @@ class Attribute(object):
""" """
*Read-only* representation of an attribute. *Read-only* representation of an attribute.
Instances of this class are frequently used for introspection purposes
like:
- `fields` returns a tuple of them.
- Validators get them passed as the first argument.
- The *field transformer* hook receives a list of them.
:attribute name: The name of the attribute. :attribute name: The name of the attribute.
:attribute inherited: Whether or not that attribute has been inherited from :attribute inherited: Whether or not that attribute has been inherited from
a base class. a base class.
@ -2303,10 +2408,17 @@ class Attribute(object):
return self.eq and self.order return self.eq and self.order
# Don't use attr.assoc since fields(Attribute) doesn't work # Don't use attr.evolve since fields(Attribute) doesn't work
def _assoc(self, **changes): def evolve(self, **changes):
""" """
Copy *self* and apply *changes*. Copy *self* and apply *changes*.
This works similarly to `attr.evolve` but that function does not work
with ``Attribute``.
It is mainly meant to be used for `transform-fields`.
.. versionadded:: 20.3.0
""" """
new = copy.copy(self) new = copy.copy(self)

2
lib/attr/_next_gen.py

@ -33,6 +33,7 @@ def define(
auto_detect=True, auto_detect=True,
getstate_setstate=None, getstate_setstate=None,
on_setattr=None, on_setattr=None,
field_transformer=None,
): ):
r""" r"""
The only behavioral differences are the handling of the *auto_attribs* The only behavioral differences are the handling of the *auto_attribs*
@ -72,6 +73,7 @@ def define(
collect_by_mro=True, collect_by_mro=True,
getstate_setstate=getstate_setstate, getstate_setstate=getstate_setstate,
on_setattr=on_setattr, on_setattr=on_setattr,
field_transformer=field_transformer,
) )
def wrap(cls): def wrap(cls):

Loading…
Cancel
Save