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. 158
      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)
* 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 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
__version__ = "20.2.0"
__version__ = "20.3.0"
__version_info__ = VersionInfo._from_version_string(__version__)
__title__ = "attrs"

10
lib/attr/__init__.pyi

@ -46,6 +46,7 @@ _OnSetAttrType = Callable[[Any, Attribute[Any], Any], Any]
_OnSetAttrArgType = Union[
_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
# or tuple, but those are invariant and so would prevent subtypes of
# _ValidatorType from working when passed in a list or tuple.
@ -272,8 +273,10 @@ def attrs(
eq: Optional[bool] = ...,
order: Optional[bool] = ...,
auto_detect: bool = ...,
collect_by_mro: bool = ...,
getstate_setstate: Optional[bool] = ...,
on_setattr: Optional[_OnSetAttrArgType] = ...,
field_transformer: Optional[_FieldTransformer] = ...,
) -> _C: ...
@overload
def attrs(
@ -295,8 +298,10 @@ def attrs(
eq: Optional[bool] = ...,
order: Optional[bool] = ...,
auto_detect: bool = ...,
collect_by_mro: bool = ...,
getstate_setstate: Optional[bool] = ...,
on_setattr: Optional[_OnSetAttrArgType] = ...,
field_transformer: Optional[_FieldTransformer] = ...,
) -> Callable[[_C], _C]: ...
@overload
def define(
@ -319,6 +324,7 @@ def define(
auto_detect: bool = ...,
getstate_setstate: Optional[bool] = ...,
on_setattr: Optional[_OnSetAttrArgType] = ...,
field_transformer: Optional[_FieldTransformer] = ...,
) -> _C: ...
@overload
def define(
@ -341,6 +347,7 @@ def define(
auto_detect: bool = ...,
getstate_setstate: Optional[bool] = ...,
on_setattr: Optional[_OnSetAttrArgType] = ...,
field_transformer: Optional[_FieldTransformer] = ...,
) -> Callable[[_C], _C]: ...
mutable = define
@ -381,7 +388,9 @@ def make_class(
auto_exc: bool = ...,
eq: Optional[bool] = ...,
order: Optional[bool] = ...,
collect_by_mro: bool = ...,
on_setattr: Optional[_OnSetAttrArgType] = ...,
field_transformer: Optional[_FieldTransformer] = ...,
) -> type: ...
# _funcs --
@ -397,6 +406,7 @@ def asdict(
filter: Optional[_FilterType[Any]] = ...,
dict_factory: Type[Mapping[Any, Any]] = ...,
retain_collection_types: bool = ...,
value_serializer: Optional[Callable[[type, Attribute, Any], Any]] = ...,
) -> Dict[str, Any]: ...
# 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.
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
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
# why not do the easy/fast thing?)
if PYPY: # pragma: no cover
if PYPY:
def set_closure_cell(cell, value):
cell.__setstate__((value,))

76
lib/attr/_funcs.py

@ -13,6 +13,7 @@ def asdict(
filter=None,
dict_factory=dict,
retain_collection_types=False,
value_serializer=None,
):
"""
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
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*
@ -40,6 +45,7 @@ def asdict(
.. versionadded:: 16.0.0 *dict_factory*
.. versionadded:: 16.1.0 *retain_collection_types*
.. versionadded:: 20.3.0 *value_serializer*
"""
attrs = fields(inst.__class__)
rv = dict_factory()
@ -47,17 +53,30 @@ def asdict(
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, 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
rv[a.name] = cf(
[
_asdict_anything(
i, filter, dict_factory, retain_collection_types
i,
filter,
dict_factory,
retain_collection_types,
value_serializer,
)
for i in v
]
@ -67,10 +86,18 @@ def asdict(
rv[a.name] = df(
(
_asdict_anything(
kk, filter, df, retain_collection_types
kk,
filter,
df,
retain_collection_types,
value_serializer,
),
_asdict_anything(
vv, filter, df, retain_collection_types
vv,
filter,
df,
retain_collection_types,
value_serializer,
),
)
for kk, vv in iteritems(v)
@ -82,19 +109,36 @@ def asdict(
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.
"""
if getattr(val.__class__, "__attrs_attrs__", None) is not None:
# Attrs class.
rv = asdict(val, True, filter, dict_factory, retain_collection_types)
elif isinstance(val, (tuple, list, set)):
rv = asdict(
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
rv = cf(
[
_asdict_anything(
i, filter, dict_factory, retain_collection_types
i,
filter,
dict_factory,
retain_collection_types,
value_serializer,
)
for i in val
]
@ -103,13 +147,20 @@ def _asdict_anything(val, filter, dict_factory, retain_collection_types):
df = dict_factory
rv = df(
(
_asdict_anything(kk, filter, df, retain_collection_types),
_asdict_anything(vv, filter, df, retain_collection_types),
_asdict_anything(
kk, filter, df, retain_collection_types, value_serializer
),
_asdict_anything(
vv, filter, df, retain_collection_types, 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
@ -164,7 +215,7 @@ def astuple(
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
rv.append(
cf(
@ -209,6 +260,7 @@ def astuple(
rv.append(v)
else:
rv.append(v)
return rv if tuple_factory is list else tuple_factory(rv)

158
lib/attr/_make.py

@ -12,6 +12,7 @@ from operator import itemgetter
from . import _config, setters
from ._compat import (
PY2,
PYPY,
isclass,
iteritems,
metadata_proxy,
@ -223,6 +224,7 @@ def attrib(
.. deprecated:: 19.2.0 *cmp* Removal on or after 2021-06-01.
.. versionadded:: 19.2.0 *eq* and *order*
.. 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)
@ -373,7 +375,7 @@ def _collect_base_attrs(cls, taken_attr_names):
if a.inherited or a.name in taken_attr_names:
continue
a = a._assoc(inherited=True)
a = a.evolve(inherited=True)
base_attrs.append(a)
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:
continue
a = a._assoc(inherited=True)
a = a.evolve(inherited=True)
taken_attr_names.add(a.name)
base_attrs.append(a)
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
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.
@ -451,6 +455,7 @@ def _transform_attrs(cls, these, auto_attribs, kw_only, collect_by_mro):
continue
annot_names.add(attr_name)
a = cd.get(attr_name, NOTHING)
if not isinstance(a, _CountingAttr):
if a is NOTHING:
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)
if kw_only:
own_attrs = [a._assoc(kw_only=True) for a in own_attrs]
base_attrs = [a._assoc(kw_only=True) for a in base_attrs]
own_attrs = [a.evolve(kw_only=True) for a in own_attrs]
base_attrs = [a.evolve(kw_only=True) for a in base_attrs]
attrs = AttrsClass(base_attrs + own_attrs)
@ -518,14 +523,34 @@ def _transform_attrs(cls, these, auto_attribs, kw_only, collect_by_mro):
if had_default is False and a.default is not NOTHING:
had_default = True
if field_transformer is not None:
attrs = field_transformer(cls, attrs)
return _Attributes((attrs, base_attrs, base_attr_map))
def _frozen_setattrs(self, name, value):
"""
Attached to frozen classes as __setattr__.
"""
raise FrozenInstanceError()
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__.
"""
raise FrozenInstanceError()
def _frozen_delattrs(self, name):
@ -574,9 +599,15 @@ class _ClassBuilder(object):
collect_by_mro,
on_setattr,
has_custom_setattr,
field_transformer,
):
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
@ -1001,6 +1032,7 @@ def attrs(
collect_by_mro=False,
getstate_setstate=None,
on_setattr=None,
field_transformer=None,
):
r"""
A class decorator that adds `dunder
@ -1093,12 +1125,14 @@ def attrs(
argument name. If a ``__attrs_post_init__`` method exists on the
class, it will be called after the class is fully initialized.
: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
someone attempts to modify a frozen instance,
`attr.exceptions.FrozenInstanceError` is raised.
Please note:
.. note::
1. This is achieved by installing a custom ``__setattr__`` method
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
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
the new value.
@ -1194,6 +1228,11 @@ def attrs(
If a list of callables is passed, they're automatically wrapped in an
`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.1.0 *frozen*
@ -1223,6 +1262,7 @@ def attrs(
.. versionadded:: 20.1.0 *collect_by_mro*
.. versionadded:: 20.1.0 *getstate_setstate*
.. versionadded:: 20.1.0 *on_setattr*
.. versionadded:: 20.3.0 *field_transformer*
"""
if auto_detect and PY2:
raise PythonTooOldError(
@ -1269,6 +1309,7 @@ def attrs(
collect_by_mro,
on_setattr,
has_own_setattr,
field_transformer,
)
if _determine_whether_to_implement(
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(
attrs,
frozen,
@ -2157,14 +2255,14 @@ def _attrs_to_init_script(
args = ", ".join(args)
if kw_only_args:
if PY2:
raise PythonTooOldError(
"Keyword-only arguments only work on Python 3 and later."
)
lines = _unpack_kw_only_lines_py2(kw_only_args) + lines
args += "{leading_comma}*, {kw_only_args}".format(
leading_comma=", " if args else "",
kw_only_args=", ".join(kw_only_args),
)
args += "%s**_kw_only" % (", " if args else "",) # leading comma
else:
args += "%s*, %s" % (
", " if args else "", # leading comma
", ".join(kw_only_args), # kw_only args
)
return (
"""\
def __init__(self, {args}):
@ -2181,6 +2279,13 @@ class Attribute(object):
"""
*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 inherited: Whether or not that attribute has been inherited from
a base class.
@ -2303,10 +2408,17 @@ class Attribute(object):
return self.eq and self.order
# Don't use attr.assoc since fields(Attribute) doesn't work
def _assoc(self, **changes):
# Don't use attr.evolve since fields(Attribute) doesn't work
def evolve(self, **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)

2
lib/attr/_next_gen.py

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

Loading…
Cancel
Save