From a37e7812d49ac16938f0e18acb2df7c9382d1203 Mon Sep 17 00:00:00 2001 From: Mitchell Hentges Date: Fri, 21 Jan 2022 18:21:56 +0000 Subject: [PATCH] Bug 1748737: Bump attrs to be compatible with mochitest r=ahal A bunch of modern packages (`pytest`, `twisted`, `automat`) all need `attrs==19.2.0` (or newer). We _could_ bump `attrs` all the way to the modern `21.4.0` version, but I'd like to defer that upgrade risk, since there's a lot of backwards-incompatible changes and deprecations. So, lightly bump it to `19.2.0`. As part of bumping it, `pytest` is no longer compatible. The earliest candidate that seems to be compatible is `pytest` 4.6.6, which boasts in its release notes that it's resolved some deprecation warnings against `attrs>=19.2.0`. Once `pytest` was bumped, it needed a newer version of `pluggy`, which itself has dependencies. Since we're using hashes in `tox_requirements.txt`, all dependencies needed to be hashed as well. Differential Revision: https://phabricator.services.mozilla.com/D135178 --- build/python-test_virtualenv_packages.txt | 2 +- third_party/python/attrs/attr/__init__.py | 5 +- third_party/python/attrs/attr/__init__.pyi | 63 ++- third_party/python/attrs/attr/_compat.py | 119 +++- third_party/python/attrs/attr/_funcs.py | 8 +- third_party/python/attrs/attr/_make.py | 526 ++++++++++-------- third_party/python/attrs/attr/_version.py | 85 +++ third_party/python/attrs/attr/_version.pyi | 9 + third_party/python/attrs/attr/converters.py | 4 +- third_party/python/attrs/attr/exceptions.py | 19 +- third_party/python/attrs/attr/exceptions.pyi | 8 + third_party/python/attrs/attr/filters.py | 10 +- third_party/python/attrs/attr/validators.py | 126 ++++- third_party/python/attrs/attr/validators.pyi | 60 +- .../attrs/attrs-19.1.0.dist-info/RECORD | 20 - .../LICENSE | 0 .../METADATA | 107 ++-- .../attrs/attrs-19.2.0.dist-info/RECORD | 22 + .../WHEEL | 2 +- .../top_level.txt | 0 third_party/python/requirements.in | 2 +- third_party/python/requirements.txt | 6 +- tools/lint/tox/tox_requirements.txt | 5 +- 23 files changed, 842 insertions(+), 366 deletions(-) create mode 100644 third_party/python/attrs/attr/_version.py create mode 100644 third_party/python/attrs/attr/_version.pyi delete mode 100644 third_party/python/attrs/attrs-19.1.0.dist-info/RECORD rename third_party/python/attrs/{attrs-19.1.0.dist-info => attrs-19.2.0.dist-info}/LICENSE (100%) rename third_party/python/attrs/{attrs-19.1.0.dist-info => attrs-19.2.0.dist-info}/METADATA (60%) create mode 100644 third_party/python/attrs/attrs-19.2.0.dist-info/RECORD rename third_party/python/attrs/{attrs-19.1.0.dist-info => attrs-19.2.0.dist-info}/WHEEL (70%) rename third_party/python/attrs/{attrs-19.1.0.dist-info => attrs-19.2.0.dist-info}/top_level.txt (100%) diff --git a/build/python-test_virtualenv_packages.txt b/build/python-test_virtualenv_packages.txt index 4218f7fc45a1..9321a02d2aa1 100644 --- a/build/python-test_virtualenv_packages.txt +++ b/build/python-test_virtualenv_packages.txt @@ -1,2 +1,2 @@ vendored:third_party/python/glean_parser -pypi:pytest==3.6.2 +pypi:pytest==4.6.6 diff --git a/third_party/python/attrs/attr/__init__.py b/third_party/python/attrs/attr/__init__.py index 0ebe5197a03a..66ce10920b92 100644 --- a/third_party/python/attrs/attr/__init__.py +++ b/third_party/python/attrs/attr/__init__.py @@ -16,9 +16,11 @@ from ._make import ( make_class, validate, ) +from ._version import VersionInfo -__version__ = "19.1.0" +__version__ = "19.2.0" +__version_info__ = VersionInfo._from_version_string(__version__) __title__ = "attrs" __description__ = "Classes Without Boilerplate" @@ -37,6 +39,7 @@ s = attributes = attrs ib = attr = attrib dataclass = partial(attrs, auto_attribs=True) # happy Easter ;) + __all__ = [ "Attribute", "Factory", diff --git a/third_party/python/attrs/attr/__init__.pyi b/third_party/python/attrs/attr/__init__.pyi index fcb93b18e36f..d19a05546640 100644 --- a/third_party/python/attrs/attr/__init__.pyi +++ b/third_party/python/attrs/attr/__init__.pyi @@ -20,12 +20,27 @@ from . import filters as filters from . import converters as converters from . import validators as validators +from ._version import VersionInfo + +__version__: str +__version_info__: VersionInfo +__title__: str +__description__: str +__url__: str +__uri__: str +__author__: str +__email__: str +__license__: str +__copyright__: str + _T = TypeVar("_T") _C = TypeVar("_C", bound=type) _ValidatorType = Callable[[Any, Attribute[_T], _T], Any] _ConverterType = Callable[[Any], _T] _FilterType = Callable[[Attribute[_T], _T], bool] +_ReprType = Callable[[Any], str] +_ReprArgType = Union[bool, _ReprType] # 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. @@ -49,18 +64,16 @@ class Attribute(Generic[_T]): name: str default: Optional[_T] validator: Optional[_ValidatorType[_T]] - repr: bool + repr: _ReprArgType cmp: bool + eq: bool + order: bool hash: Optional[bool] init: bool converter: Optional[_ConverterType[_T]] metadata: Dict[Any, Any] type: Optional[Type[_T]] kw_only: bool - def __lt__(self, x: Attribute[_T]) -> bool: ... - def __le__(self, x: Attribute[_T]) -> bool: ... - def __gt__(self, x: Attribute[_T]) -> bool: ... - def __ge__(self, x: Attribute[_T]) -> bool: ... # NOTE: We had several choices for the annotation to use for type arg: # 1) Type[_T] @@ -89,16 +102,17 @@ class Attribute(Generic[_T]): def attrib( default: None = ..., validator: None = ..., - repr: bool = ..., - cmp: bool = ..., + repr: _ReprArgType = ..., + cmp: Optional[bool] = ..., hash: Optional[bool] = ..., init: bool = ..., - convert: None = ..., metadata: Optional[Mapping[Any, Any]] = ..., type: None = ..., converter: None = ..., factory: None = ..., kw_only: bool = ..., + eq: Optional[bool] = ..., + order: Optional[bool] = ..., ) -> Any: ... # This form catches an explicit None or no default and infers the type from the other arguments. @@ -106,16 +120,17 @@ def attrib( def attrib( default: None = ..., validator: Optional[_ValidatorArgType[_T]] = ..., - repr: bool = ..., - cmp: bool = ..., + repr: _ReprArgType = ..., + cmp: Optional[bool] = ..., hash: Optional[bool] = ..., init: bool = ..., - convert: Optional[_ConverterType[_T]] = ..., metadata: Optional[Mapping[Any, Any]] = ..., type: Optional[Type[_T]] = ..., converter: Optional[_ConverterType[_T]] = ..., factory: Optional[Callable[[], _T]] = ..., kw_only: bool = ..., + eq: Optional[bool] = ..., + order: Optional[bool] = ..., ) -> _T: ... # This form catches an explicit default argument. @@ -123,16 +138,17 @@ def attrib( def attrib( default: _T, validator: Optional[_ValidatorArgType[_T]] = ..., - repr: bool = ..., - cmp: bool = ..., + repr: _ReprArgType = ..., + cmp: Optional[bool] = ..., hash: Optional[bool] = ..., init: bool = ..., - convert: Optional[_ConverterType[_T]] = ..., metadata: Optional[Mapping[Any, Any]] = ..., type: Optional[Type[_T]] = ..., converter: Optional[_ConverterType[_T]] = ..., factory: Optional[Callable[[], _T]] = ..., kw_only: bool = ..., + eq: Optional[bool] = ..., + order: Optional[bool] = ..., ) -> _T: ... # This form covers type=non-Type: e.g. forward references (str), Any @@ -140,16 +156,17 @@ def attrib( def attrib( default: Optional[_T] = ..., validator: Optional[_ValidatorArgType[_T]] = ..., - repr: bool = ..., - cmp: bool = ..., + repr: _ReprArgType = ..., + cmp: Optional[bool] = ..., hash: Optional[bool] = ..., init: bool = ..., - convert: Optional[_ConverterType[_T]] = ..., metadata: Optional[Mapping[Any, Any]] = ..., type: object = ..., converter: Optional[_ConverterType[_T]] = ..., factory: Optional[Callable[[], _T]] = ..., kw_only: bool = ..., + eq: Optional[bool] = ..., + order: Optional[bool] = ..., ) -> Any: ... @overload def attrs( @@ -157,7 +174,7 @@ def attrs( these: Optional[Dict[str, Any]] = ..., repr_ns: Optional[str] = ..., repr: bool = ..., - cmp: bool = ..., + cmp: Optional[bool] = ..., hash: Optional[bool] = ..., init: bool = ..., slots: bool = ..., @@ -168,6 +185,8 @@ def attrs( kw_only: bool = ..., cache_hash: bool = ..., auto_exc: bool = ..., + eq: Optional[bool] = ..., + order: Optional[bool] = ..., ) -> _C: ... @overload def attrs( @@ -175,7 +194,7 @@ def attrs( these: Optional[Dict[str, Any]] = ..., repr_ns: Optional[str] = ..., repr: bool = ..., - cmp: bool = ..., + cmp: Optional[bool] = ..., hash: Optional[bool] = ..., init: bool = ..., slots: bool = ..., @@ -186,6 +205,8 @@ def attrs( kw_only: bool = ..., cache_hash: bool = ..., auto_exc: bool = ..., + eq: Optional[bool] = ..., + order: Optional[bool] = ..., ) -> Callable[[_C], _C]: ... # TODO: add support for returning NamedTuple from the mypy plugin @@ -204,7 +225,7 @@ def make_class( bases: Tuple[type, ...] = ..., repr_ns: Optional[str] = ..., repr: bool = ..., - cmp: bool = ..., + cmp: Optional[bool] = ..., hash: Optional[bool] = ..., init: bool = ..., slots: bool = ..., @@ -215,6 +236,8 @@ def make_class( kw_only: bool = ..., cache_hash: bool = ..., auto_exc: bool = ..., + eq: Optional[bool] = ..., + order: Optional[bool] = ..., ) -> type: ... # _funcs -- diff --git a/third_party/python/attrs/attr/_compat.py b/third_party/python/attrs/attr/_compat.py index 9a99dcd96cb5..a915db8ebeb4 100644 --- a/third_party/python/attrs/attr/_compat.py +++ b/third_party/python/attrs/attr/_compat.py @@ -20,7 +20,7 @@ else: if PY2: from UserDict import IterableUserDict - from collections import Mapping, Sequence # noqa + from collections import Mapping, Sequence # We 'bundle' isclass instead of using inspect as importing inspect is # fairly expensive (order of 10-15 ms for a modern machine in 2016) @@ -106,7 +106,8 @@ else: # Python 3 and later. consequences of not setting the cell on Python 2. """ warnings.warn( - "Missing ctypes. Some features like bare super() or accessing " + "Running interpreter doesn't sufficiently support code object " + "introspection. Some features like bare super() or accessing " "__class__ will not work with slotted classes.", RuntimeWarning, stacklevel=2, @@ -124,36 +125,106 @@ else: # Python 3 and later. return types.MappingProxyType(dict(d)) -def import_ctypes(): - """ - Moved into a function for testability. - """ - import ctypes - - return ctypes - - def make_set_closure_cell(): + """Return a function of two arguments (cell, value) which sets + the value stored in the closure cell `cell` to `value`. """ - Moved into a function for testability. - """ + # pypy makes this easy. (It also supports the logic below, but + # why not do the easy/fast thing?) if PYPY: # pragma: no cover def set_closure_cell(cell, value): cell.__setstate__((value,)) - else: - try: - ctypes = import_ctypes() + return set_closure_cell - set_closure_cell = ctypes.pythonapi.PyCell_Set - set_closure_cell.argtypes = (ctypes.py_object, ctypes.py_object) - set_closure_cell.restype = ctypes.c_int - except Exception: - # We try best effort to set the cell, but sometimes it's not - # possible. For example on Jython or on GAE. - set_closure_cell = just_warn - return set_closure_cell + # Otherwise gotta do it the hard way. + + # Create a function that will set its first cellvar to `value`. + def set_first_cellvar_to(value): + x = value + return + + # This function will be eliminated as dead code, but + # not before its reference to `x` forces `x` to be + # represented as a closure cell rather than a local. + def force_x_to_be_a_cell(): # pragma: no cover + return x + + try: + # Extract the code object and make sure our assumptions about + # the closure behavior are correct. + if PY2: + co = set_first_cellvar_to.func_code + else: + co = set_first_cellvar_to.__code__ + if co.co_cellvars != ("x",) or co.co_freevars != (): + raise AssertionError # pragma: no cover + + # Convert this code object to a code object that sets the + # function's first _freevar_ (not cellvar) to the argument. + if sys.version_info >= (3, 8): + # CPython 3.8+ has an incompatible CodeType signature + # (added a posonlyargcount argument) but also added + # CodeType.replace() to do this without counting parameters. + set_first_freevar_code = co.replace( + co_cellvars=co.co_freevars, co_freevars=co.co_cellvars + ) + else: + args = [co.co_argcount] + if not PY2: + args.append(co.co_kwonlyargcount) + args.extend( + [ + co.co_nlocals, + co.co_stacksize, + co.co_flags, + co.co_code, + co.co_consts, + co.co_names, + co.co_varnames, + co.co_filename, + co.co_name, + co.co_firstlineno, + co.co_lnotab, + # These two arguments are reversed: + co.co_cellvars, + co.co_freevars, + ] + ) + set_first_freevar_code = types.CodeType(*args) + + def set_closure_cell(cell, value): + # Create a function using the set_first_freevar_code, + # whose first closure cell is `cell`. Calling it will + # change the value of that cell. + setter = types.FunctionType( + set_first_freevar_code, {}, "setter", (), (cell,) + ) + # And call it to set the cell. + setter(value) + + # Make sure it works on this interpreter: + def make_func_with_cell(): + x = None + + def func(): + return x # pragma: no cover + + return func + + if PY2: + cell = make_func_with_cell().func_closure[0] + else: + cell = make_func_with_cell().__closure__[0] + set_closure_cell(cell, 100) + if cell.cell_contents != 100: + raise AssertionError # pragma: no cover + + except Exception: + return just_warn + else: + return set_closure_cell set_closure_cell = make_set_closure_cell() diff --git a/third_party/python/attrs/attr/_funcs.py b/third_party/python/attrs/attr/_funcs.py index b61d2394124d..c077e4284f65 100644 --- a/third_party/python/attrs/attr/_funcs.py +++ b/third_party/python/attrs/attr/_funcs.py @@ -24,7 +24,7 @@ def asdict( ``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 `attr.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 @@ -130,7 +130,7 @@ def astuple( ``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 `attr.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. @@ -219,7 +219,7 @@ def has(cls): :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 @@ -239,7 +239,7 @@ def assoc(inst, **changes): class. .. deprecated:: 17.1.0 - Use :func:`evolve` instead. + Use `evolve` instead. """ import warnings diff --git a/third_party/python/attrs/attr/_make.py b/third_party/python/attrs/attr/_make.py index 827175a46058..5bdd46886700 100644 --- a/third_party/python/attrs/attr/_make.py +++ b/third_party/python/attrs/attr/_make.py @@ -1,10 +1,10 @@ from __future__ import absolute_import, division, print_function import copy -import hashlib import linecache import sys import threading +import uuid import warnings from operator import itemgetter @@ -42,6 +42,9 @@ _hash_cache_field = "_attrs_cached_hash" _empty_metadata_singleton = metadata_proxy({}) +# Unique object for unequivocal getattr() defaults. +_sentinel = object() + class _Nothing(object): """ @@ -71,15 +74,16 @@ def attrib( default=NOTHING, validator=None, repr=True, - cmp=True, + cmp=None, hash=None, init=True, - convert=None, metadata=None, type=None, converter=None, factory=None, kw_only=False, + eq=None, + order=None, ): """ Create a new attribute on a class. @@ -87,30 +91,30 @@ def attrib( .. warning:: Does *not* do anything unless the class is also decorated with - :func:`attr.s`! + `attr.s`! :param default: A value that is used if an ``attrs``-generated ``__init__`` is used and no value is passed while instantiating or the attribute is excluded using ``init=False``. - If the value is an instance of :class:`Factory`, its callable will be + If the value is an instance of `Factory`, its callable will be used to construct a new value (useful for mutable data types like lists or dicts). If a default is not set (or set manually to ``attr.NOTHING``), a value - *must* be supplied when instantiating; otherwise a :exc:`TypeError` + *must* be supplied when instantiating; otherwise a `TypeError` will be raised. The default can also be set using decorator notation as shown below. - :type default: Any value. + :type default: Any value :param callable factory: Syntactic sugar for ``default=attr.Factory(callable)``. - :param validator: :func:`callable` that is called by ``attrs``-generated + :param validator: `callable` that is called by ``attrs``-generated ``__init__`` methods after the instance has been initialized. They - receive the initialized instance, the :class:`Attribute`, and the + receive the initialized instance, the `Attribute`, and the passed value. The return value is *not* inspected so the validator has to throw an @@ -120,18 +124,29 @@ def attrib( all pass. Validators can be globally disabled and re-enabled using - :func:`get_run_validators`. + `get_run_validators`. The validator can also be set using decorator notation as shown below. :type validator: ``callable`` or a ``list`` of ``callable``\\ s. - :param bool repr: Include this attribute in the generated ``__repr__`` - method. - :param bool cmp: Include this attribute in the generated comparison methods - (``__eq__`` et al). + :param repr: Include this attribute in the generated ``__repr__`` + method. If ``True``, include the attribute; if ``False``, omit it. By + default, the built-in ``repr()`` function is used. To override how the + attribute value is formatted, pass a ``callable`` that takes a single + value and returns a string. Note that the resulting string is used + as-is, i.e. it will be used directly *instead* of calling ``repr()`` + (the default). + :type repr: a ``bool`` or a ``callable`` to use a custom function. + :param bool eq: If ``True`` (default), include this attribute in the + generated ``__eq__`` and ``__ne__`` methods that check two instances + for equality. + :param bool order: If ``True`` (default), include this attributes in the + generated ``__lt__``, ``__le__``, ``__gt__`` and ``__ge__`` methods. + :param bool cmp: Setting to ``True`` is equivalent to setting ``eq=True, + order=True``. Deprecated in favor of *eq* and *order*. :param hash: Include this attribute in the generated ``__hash__`` - method. If ``None`` (default), mirror *cmp*'s value. This is the + method. If ``None`` (default), mirror *eq*'s value. This is the correct behavior according the Python spec. Setting this value to anything else than ``None`` is *discouraged*. :type hash: ``bool`` or ``None`` @@ -139,13 +154,13 @@ def attrib( method. It is possible to set this to ``False`` and set a default value. In that case this attributed is unconditionally initialized with the specified default value or factory. - :param callable converter: :func:`callable` that is called by + :param callable converter: `callable` that is called by ``attrs``-generated ``__init__`` methods to converter attribute's value to the desired format. It is given the passed-in value, and the returned value will be used as the new value of the attribute. The value is converted before being passed to the validator, if any. :param metadata: An arbitrary mapping, to be used by third-party - components. See :ref:`extending_metadata`. + components. See `extending_metadata`. :param type: The type of the attribute. In Python 3.6 or greater, the preferred method to specify the type is using a variable annotation (see `PEP 526 `_). @@ -155,7 +170,7 @@ def attrib( Please note that ``attrs`` doesn't do anything with this metadata by itself. You can use it as part of your own code or for - :doc:`static type checking `. + `static type checking `. :param kw_only: Make this attribute keyword-only (Python 3+) in the generated ``__init__`` (if ``init`` is ``False``, this parameter is ignored). @@ -164,7 +179,7 @@ def attrib( .. versionadded:: 16.3.0 *metadata* .. versionchanged:: 17.1.0 *validator* can be a ``list`` now. .. versionchanged:: 17.1.0 - *hash* is ``None`` and therefore mirrors *cmp* by default. + *hash* is ``None`` and therefore mirrors *eq* by default. .. versionadded:: 17.3.0 *type* .. deprecated:: 17.4.0 *convert* .. versionadded:: 17.4.0 *converter* as a replacement for the deprecated @@ -172,26 +187,18 @@ def attrib( .. versionadded:: 18.1.0 ``factory=f`` is syntactic sugar for ``default=attr.Factory(f)``. .. versionadded:: 18.2.0 *kw_only* + .. versionchanged:: 19.2.0 *convert* keyword argument removed + .. versionchanged:: 19.2.0 *repr* also accepts a custom callable. + .. deprecated:: 19.2.0 *cmp* Removal on or after 2021-06-01. + .. versionadded:: 19.2.0 *eq* and *order* """ + eq, order = _determine_eq_order(cmp, eq, order) + if hash is not None and hash is not True and hash is not False: raise TypeError( "Invalid value for hash. Must be True, False, or None." ) - if convert is not None: - if converter is not None: - raise RuntimeError( - "Can't pass both `convert` and `converter`. " - "Please use `converter` only." - ) - warnings.warn( - "The `convert` argument is deprecated in favor of `converter`. " - "It will be removed after 2019/01.", - DeprecationWarning, - stacklevel=2, - ) - converter = convert - if factory is not None: if default is not NOTHING: raise ValueError( @@ -209,13 +216,15 @@ def attrib( default=default, validator=validator, repr=repr, - cmp=cmp, + cmp=None, hash=hash, init=init, converter=converter, metadata=metadata, type=type, kw_only=kw_only, + eq=eq, + order=order, ) @@ -385,38 +394,20 @@ def _transform_attrs(cls, these, auto_attribs, kw_only): attrs = AttrsClass(base_attrs + own_attrs) + # Mandatory vs non-mandatory attr order only matters when they are part of + # the __init__ signature and when they aren't kw_only (which are moved to + # the end and can be mandatory or non-mandatory in any order, as they will + # be specified as keyword args anyway). Check the order of those attrs: had_default = False - was_kw_only = False - for a in attrs: - if ( - was_kw_only is False - and had_default is True - and a.default is NOTHING - and a.init is True - and a.kw_only is False - ): + for a in (a for a in attrs if a.init is not False and a.kw_only is False): + if had_default is True and a.default is NOTHING: raise ValueError( "No mandatory attributes allowed after an attribute with a " "default value or factory. Attribute in question: %r" % (a,) ) - elif ( - had_default is False - and a.default is not NOTHING - and a.init is not False - and - # Keyword-only attributes without defaults can be specified - # after keyword-only attributes with defaults. - a.kw_only is False - ): + + if had_default is False and a.default is not NOTHING: had_default = True - if was_kw_only is True and a.kw_only is False and a.init is True: - raise ValueError( - "Non keyword-only attributes are not allowed after a " - "keyword-only attribute (unless they are init=False). " - "Attribute in question: {a!r}".format(a=a) - ) - if was_kw_only is False and a.init is True and a.kw_only is True: - was_kw_only = True return _Attributes((attrs, base_attrs, base_attr_map)) @@ -518,7 +509,7 @@ class _ClassBuilder(object): for name in self._attr_names: if ( name not in base_names - and getattr(cls, name, None) is not None + and getattr(cls, name, _sentinel) != _sentinel ): try: delattr(cls, name) @@ -676,7 +667,10 @@ class _ClassBuilder(object): def add_hash(self): self._cls_dict["__hash__"] = self._add_method_dunders( _make_hash( - self._attrs, frozen=self._frozen, cache_hash=self._cache_hash + self._cls, + self._attrs, + frozen=self._frozen, + cache_hash=self._cache_hash, ) ) @@ -685,6 +679,7 @@ class _ClassBuilder(object): def add_init(self): self._cls_dict["__init__"] = self._add_method_dunders( _make_init( + self._cls, self._attrs, self._has_post_init, self._frozen, @@ -697,13 +692,22 @@ class _ClassBuilder(object): return self - def add_cmp(self): + def add_eq(self): cd = self._cls_dict - cd["__eq__"], cd["__ne__"], cd["__lt__"], cd["__le__"], cd[ - "__gt__" - ], cd["__ge__"] = ( - self._add_method_dunders(meth) for meth in _make_cmp(self._attrs) + cd["__eq__"], cd["__ne__"] = ( + self._add_method_dunders(meth) + for meth in _make_eq(self._cls, self._attrs) + ) + + return self + + def add_order(self): + cd = self._cls_dict + + cd["__lt__"], cd["__le__"], cd["__gt__"], cd["__ge__"] = ( + self._add_method_dunders(meth) + for meth in _make_order(self._cls, self._attrs) ) return self @@ -727,12 +731,45 @@ class _ClassBuilder(object): return method +_CMP_DEPRECATION = ( + "The usage of `cmp` is deprecated and will be removed on or after " + "2021-06-01. Please use `eq` and `order` instead." +) + + +def _determine_eq_order(cmp, eq, order): + """ + Validate the combination of *cmp*, *eq*, and *order*. Derive the effective + values of eq and order. + """ + if cmp is not None and any((eq is not None, order is not None)): + raise ValueError("Don't mix `cmp` with `eq' and `order`.") + + # cmp takes precedence due to bw-compatibility. + if cmp is not None: + warnings.warn(_CMP_DEPRECATION, DeprecationWarning, stacklevel=3) + + return cmp, cmp + + # If left None, equality is on and ordering mirrors equality. + if eq is None: + eq = True + + if order is None: + order = eq + + if eq is False and order is True: + raise ValueError("`order` can only be True if `eq` is True too.") + + return eq, order + + def attrs( maybe_cls=None, these=None, repr_ns=None, repr=True, - cmp=True, + cmp=None, hash=None, init=True, slots=False, @@ -743,13 +780,15 @@ def attrs( kw_only=False, cache_hash=False, auto_exc=False, + eq=None, + order=None, ): r""" A class decorator that adds `dunder `_\ -methods according to the - specified attributes using :func:`attr.ib` or the *these* argument. + specified attributes using `attr.ib` or the *these* argument. - :param these: A dictionary of name to :func:`attr.ib` mappings. This is + :param these: A dictionary of name to `attr.ib` mappings. This is useful to avoid the definition of your attributes within the class body because you can't (e.g. if you want to add ``__repr__`` methods to Django models) or don't want to. @@ -757,12 +796,12 @@ def attrs( If *these* is not ``None``, ``attrs`` will *not* search the class body for attributes and will *not* remove any attributes from it. - If *these* is an ordered dict (:class:`dict` on Python 3.6+, - :class:`collections.OrderedDict` otherwise), the order is deduced from + If *these* is an ordered dict (`dict` on Python 3.6+, + `collections.OrderedDict` otherwise), the order is deduced from the order of the attributes inside *these*. Otherwise the order of the definition of the attributes is used. - :type these: :class:`dict` of :class:`str` to :func:`attr.ib` + :type these: `dict` of `str` to `attr.ib` :param str repr_ns: When using nested classes, there's no way in Python 2 to automatically detect that. Therefore it's possible to set the @@ -771,18 +810,29 @@ def attrs( representation of ``attrs`` attributes.. :param bool str: Create a ``__str__`` method that is identical to ``__repr__``. This is usually not necessary except for - :class:`Exception`\ s. - :param bool cmp: Create ``__eq__``, ``__ne__``, ``__lt__``, ``__le__``, - ``__gt__``, and ``__ge__`` methods that compare the class as if it were - a tuple of its ``attrs`` attributes. But the attributes are *only* - compared, if the types of both classes are *identical*! + `Exception`\ s. + :param bool eq: If ``True`` or ``None`` (default), add ``__eq__`` and + ``__ne__`` methods that check two instances for equality. + + They compare the instances as if they were tuples of their ``attrs`` + attributes, but only iff the types of both classes are *identical*! + :type eq: `bool` or `None` + :param bool order: If ``True``, add ``__lt__``, ``__le__``, ``__gt__``, + and ``__ge__`` methods that behave like *eq* above and allow instances + to be ordered. If ``None`` (default) mirror value of *eq*. + :type order: `bool` or `None` + :param cmp: Setting to ``True`` is equivalent to setting ``eq=True, + order=True``. Deprecated in favor of *eq* and *order*, has precedence + over them for backward-compatibility though. Must not be mixed with + *eq* or *order*. + :type cmp: `bool` or `None` :param hash: If ``None`` (default), the ``__hash__`` method is generated - according how *cmp* and *frozen* are set. + according how *eq* and *frozen* are set. 1. If *both* are True, ``attrs`` will generate a ``__hash__`` for you. - 2. If *cmp* is True and *frozen* is False, ``__hash__`` will be set to + 2. If *eq* is True and *frozen* is False, ``__hash__`` will be set to None, marking it unhashable (which it is). - 3. If *cmp* is False, ``__hash__`` will be left untouched meaning the + 3. If *eq* is False, ``__hash__`` will be left untouched meaning the ``__hash__`` method of the base class will be used (if base class is ``object``, this means it will fall back to id-based hashing.). @@ -791,20 +841,20 @@ def attrs( didn't freeze it programmatically) by passing ``True`` or not. Both of these cases are rather special and should be used carefully. - See the `Python documentation \ - `_ - and the `GitHub issue that led to the default behavior \ - `_ for more details. + See our documentation on `hashing`, Python's documentation on + `object.__hash__`, and the `GitHub issue that led to the default \ + behavior `_ for more + details. :type hash: ``bool`` or ``None`` :param bool init: Create a ``__init__`` method that initializes the ``attrs`` attributes. Leading underscores are stripped for the 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 slots_-style class that's more - memory-efficient. See :ref:`slots` for further ramifications. + :param bool slots: Create a `slotted class ` that's more + memory-efficient. :param bool frozen: Make instances immutable after initialization. If someone attempts to modify a frozen instance, - :exc:`attr.exceptions.FrozenInstanceError` is raised. + `attr.exceptions.FrozenInstanceError` is raised. Please note: @@ -813,7 +863,7 @@ def attrs( 2. True immutability is impossible in Python. - 3. This *does* have a minor a runtime performance :ref:`impact + 3. This *does* have a minor a runtime performance `impact ` when initializing new instances. In other words: ``__init__`` is slightly slower with ``frozen=True``. @@ -822,24 +872,24 @@ def attrs( circumvent that limitation by using ``object.__setattr__(self, "attribute_name", value)``. - .. _slots: https://docs.python.org/3/reference/datamodel.html#slots :param bool weakref_slot: Make instances weak-referenceable. This has no effect unless ``slots`` is also enabled. :param bool auto_attribs: If True, collect `PEP 526`_-annotated attributes (Python 3.6 and later only) from the class body. In this case, you **must** annotate every field. If ``attrs`` - encounters a field that is set to an :func:`attr.ib` but lacks a type - annotation, an :exc:`attr.exceptions.UnannotatedAttributeError` is + encounters a field that is set to an `attr.ib` but lacks a type + annotation, an `attr.exceptions.UnannotatedAttributeError` is raised. Use ``field_name: typing.Any = attr.ib(...)`` if you don't want to set a type. If you assign a value to those attributes (e.g. ``x: int = 42``), that value becomes the default value like if it were passed using - ``attr.ib(default=42)``. Passing an instance of :class:`Factory` also + ``attr.ib(default=42)``. Passing an instance of `Factory` also works as expected. - Attributes annotated as :data:`typing.ClassVar` are **ignored**. + Attributes annotated as `typing.ClassVar`, and attributes that are + neither annotated nor set to an `attr.ib` are **ignored**. .. _`PEP 526`: https://www.python.org/dev/peps/pep-0526/ :param bool kw_only: Make all attributes keyword-only (Python 3+) @@ -852,15 +902,15 @@ def attrs( fields involved in hash code computation or mutations of the objects those fields point to after object creation. If such changes occur, the behavior of the object's hash code is undefined. - :param bool auto_exc: If the class subclasses :class:`BaseException` + :param bool auto_exc: If the class subclasses `BaseException` (which implicitly includes any subclass of any exception), the following happens to behave like a well-behaved Python exceptions class: - - the values for *cmp* and *hash* are ignored and the instances compare - and hash by the instance's ids (N.B. ``attrs`` will *not* remove - existing implementations of ``__hash__`` or the equality methods. It - just won't add own ones.), + - the values for *eq*, *order*, and *hash* are ignored and the + instances compare and hash by the instance's ids (N.B. ``attrs`` will + *not* remove existing implementations of ``__hash__`` or the equality + methods. It just won't add own ones.), - all attributes that are either passed into ``__init__`` or have a default value are additionally available as a tuple in the ``args`` attribute, @@ -879,13 +929,19 @@ def attrs( .. versionadded:: 18.2.0 *weakref_slot* .. deprecated:: 18.2.0 ``__lt__``, ``__le__``, ``__gt__``, and ``__ge__`` now raise a - :class:`DeprecationWarning` if the classes compared are subclasses of + `DeprecationWarning` if the classes compared are subclasses of each other. ``__eq`` and ``__ne__`` never tried to compared subclasses to each other. + .. versionchanged:: 19.2.0 + ``__lt__``, ``__le__``, ``__gt__``, and ``__ge__`` now do not consider + subclasses comparable anymore. .. versionadded:: 18.2.0 *kw_only* .. versionadded:: 18.2.0 *cache_hash* .. versionadded:: 19.1.0 *auto_exc* + .. deprecated:: 19.2.0 *cmp* Removal on or after 2021-06-01. + .. versionadded:: 19.2.0 *eq* and *order* """ + eq, order = _determine_eq_order(cmp, eq, order) def wrap(cls): @@ -910,28 +966,30 @@ def attrs( builder.add_repr(repr_ns) if str is True: builder.add_str() - if cmp is True and not is_exc: - builder.add_cmp() + if eq is True and not is_exc: + builder.add_eq() + if order is True and not is_exc: + builder.add_order() if hash is not True and hash is not False and hash is not None: # Can't use `hash in` because 1 == True for example. raise TypeError( "Invalid value for hash. Must be True, False, or None." ) - elif hash is False or (hash is None and cmp is False): + elif hash is False or (hash is None and eq is False) or is_exc: + # Don't do anything. Should fall back to __object__'s __hash__ + # which is by id. if cache_hash: raise TypeError( "Invalid value for cache_hash. To use hash caching," " hashing must be either explicitly or implicitly " "enabled." ) - elif ( - hash is True - or (hash is None and cmp is True and frozen is True) - and is_exc is False - ): + elif hash is True or (hash is None and eq is True and frozen is True): + # Build a __hash__ if told so, or if it's safe. builder.add_hash() else: + # Raise TypeError on attempts to hash. if cache_hash: raise TypeError( "Invalid value for cache_hash. To use hash caching," @@ -997,19 +1055,44 @@ def _attrs_to_tuple(obj, attrs): return tuple(getattr(obj, a.name) for a in attrs) -def _make_hash(attrs, frozen, cache_hash): +def _generate_unique_filename(cls, func_name): + """ + Create a "filename" suitable for a function being generated. + """ + unique_id = uuid.uuid4() + extra = "" + count = 1 + + while True: + unique_filename = "".format( + func_name, + cls.__module__, + getattr(cls, "__qualname__", cls.__name__), + extra, + ) + # To handle concurrency we essentially "reserve" our spot in + # the linecache with a dummy line. The caller can then + # set this value correctly. + cache_line = (1, None, (str(unique_id),), unique_filename) + if ( + linecache.cache.setdefault(unique_filename, cache_line) + == cache_line + ): + return unique_filename + + # Looks like this spot is taken. Try again. + count += 1 + extra = "-{0}".format(count) + + +def _make_hash(cls, attrs, frozen, cache_hash): attrs = tuple( - a - for a in attrs - if a.hash is True or (a.hash is None and a.cmp is True) + a for a in attrs if a.hash is True or (a.hash is None and a.eq is True) ) tab = " " - # We cache the generated hash methods for the same kinds of attributes. - sha1 = hashlib.sha1() - sha1.update(repr(attrs).encode("utf-8")) - unique_filename = "" % (sha1.hexdigest(),) + unique_filename = _generate_unique_filename(cls, "hash") type_hash = hash(unique_filename) method_lines = ["def __hash__(self):"] @@ -1066,7 +1149,7 @@ def _add_hash(cls, attrs): """ Add a hash method to *cls*. """ - cls.__hash__ = _make_hash(attrs, frozen=False, cache_hash=False) + cls.__hash__ = _make_hash(cls, attrs, frozen=False, cache_hash=False) return cls @@ -1082,19 +1165,10 @@ def __ne__(self, other): return not result -WARNING_CMP_ISINSTANCE = ( - "Comparision of subclasses using __%s__ is deprecated and will be removed " - "in 2019." -) +def _make_eq(cls, attrs): + attrs = [a for a in attrs if a.eq] - -def _make_cmp(attrs): - attrs = [a for a in attrs if a.cmp] - - # We cache the generated eq methods for the same kinds of attributes. - sha1 = hashlib.sha1() - sha1.update(repr(attrs).encode("utf-8")) - unique_filename = "" % (sha1.hexdigest(),) + unique_filename = _generate_unique_filename(cls, "eq") lines = [ "def __eq__(self, other):", " if other.__class__ is not self.__class__:", @@ -1127,8 +1201,11 @@ def _make_cmp(attrs): script.splitlines(True), unique_filename, ) - eq = locs["__eq__"] - ne = __ne__ + return locs["__eq__"], __ne__ + + +def _make_order(cls, attrs): + attrs = [a for a in attrs if a.order] def attrs_to_tuple(obj): """ @@ -1140,67 +1217,49 @@ def _make_cmp(attrs): """ Automatically created by attrs. """ - if isinstance(other, self.__class__): - if other.__class__ is not self.__class__: - warnings.warn( - WARNING_CMP_ISINSTANCE % ("lt",), DeprecationWarning - ) + if other.__class__ is self.__class__: return attrs_to_tuple(self) < attrs_to_tuple(other) - else: - return NotImplemented + + return NotImplemented def __le__(self, other): """ Automatically created by attrs. """ - if isinstance(other, self.__class__): - if other.__class__ is not self.__class__: - warnings.warn( - WARNING_CMP_ISINSTANCE % ("le",), DeprecationWarning - ) + if other.__class__ is self.__class__: return attrs_to_tuple(self) <= attrs_to_tuple(other) - else: - return NotImplemented + + return NotImplemented def __gt__(self, other): """ Automatically created by attrs. """ - if isinstance(other, self.__class__): - if other.__class__ is not self.__class__: - warnings.warn( - WARNING_CMP_ISINSTANCE % ("gt",), DeprecationWarning - ) + if other.__class__ is self.__class__: return attrs_to_tuple(self) > attrs_to_tuple(other) - else: - return NotImplemented + + return NotImplemented def __ge__(self, other): """ Automatically created by attrs. """ - if isinstance(other, self.__class__): - if other.__class__ is not self.__class__: - warnings.warn( - WARNING_CMP_ISINSTANCE % ("ge",), DeprecationWarning - ) + if other.__class__ is self.__class__: return attrs_to_tuple(self) >= attrs_to_tuple(other) - else: - return NotImplemented - return eq, ne, __lt__, __le__, __gt__, __ge__ + return NotImplemented + + return __lt__, __le__, __gt__, __ge__ -def _add_cmp(cls, attrs=None): +def _add_eq(cls, attrs=None): """ - Add comparison methods to *cls*. + Add equality methods to *cls* with *attrs*. """ if attrs is None: attrs = cls.__attrs_attrs__ - cls.__eq__, cls.__ne__, cls.__lt__, cls.__le__, cls.__gt__, cls.__ge__ = _make_cmp( # noqa - attrs - ) + cls.__eq__, cls.__ne__ = _make_eq(cls, attrs) return cls @@ -1210,9 +1269,17 @@ _already_repring = threading.local() def _make_repr(attrs, ns): """ - Make a repr method for *attr_names* adding *ns* to the full name. + Make a repr method that includes relevant *attrs*, adding *ns* to the full + name. """ - attr_names = tuple(a.name for a in attrs if a.repr) + + # Figure out which attributes to include, and which function to use to + # format them. The a.repr value can be either bool or a custom callable. + attr_names_with_reprs = tuple( + (a.name, repr if a.repr is True else a.repr) + for a in attrs + if a.repr is not False + ) def __repr__(self): """ @@ -1244,12 +1311,14 @@ def _make_repr(attrs, ns): try: result = [class_name, "("] first = True - for name in attr_names: + for name, attr_repr in attr_names_with_reprs: if first: first = False else: result.append(", ") - result.extend((name, "=", repr(getattr(self, name, NOTHING)))) + result.extend( + (name, "=", attr_repr(getattr(self, name, NOTHING))) + ) return "".join(result) + ")" finally: working_set.remove(id(self)) @@ -1269,14 +1338,11 @@ def _add_repr(cls, ns=None, attrs=None): def _make_init( - attrs, post_init, frozen, slots, cache_hash, base_attr_map, is_exc + cls, attrs, post_init, frozen, slots, cache_hash, base_attr_map, is_exc ): attrs = [a for a in attrs if a.init or a.default is not NOTHING] - # We cache the generated init methods for the same kinds of attributes. - sha1 = hashlib.sha1() - sha1.update(repr(attrs).encode("utf-8")) - unique_filename = "".format(sha1.hexdigest()) + unique_filename = _generate_unique_filename(cls, "init") script, globs, annotations = _attrs_to_init_script( attrs, frozen, slots, post_init, cache_hash, base_attr_map, is_exc @@ -1321,7 +1387,7 @@ def fields(cls): :raise attr.exceptions.NotAnAttrsClassError: If *cls* is not an ``attrs`` class. - :rtype: tuple (with name accessors) of :class:`attr.Attribute` + :rtype: tuple (with name accessors) of `attr.Attribute` .. versionchanged:: 16.2.0 Returned tuple allows accessing the fields by name. @@ -1348,7 +1414,7 @@ def fields_dict(cls): class. :rtype: an ordered dict where keys are attribute names and values are - :class:`attr.Attribute`\\ s. This will be a :class:`dict` if it's + `attr.Attribute`\\ s. This will be a `dict` if it's naturally ordered like on Python 3.6+ or an :class:`~collections.OrderedDict` otherwise. @@ -1678,9 +1744,10 @@ class Attribute(object): :attribute name: The name of the attribute. - Plus *all* arguments of :func:`attr.ib`. + Plus *all* arguments of `attr.ib` (except for ``factory`` + which is only syntactic sugar for ``default=Factory(...)``. - For the version history of the fields, see :func:`attr.ib`. + For the version history of the fields, see `attr.ib`. """ __slots__ = ( @@ -1688,7 +1755,8 @@ class Attribute(object): "default", "validator", "repr", - "cmp", + "eq", + "order", "hash", "init", "metadata", @@ -1703,39 +1771,29 @@ class Attribute(object): default, validator, repr, - cmp, + cmp, # XXX: unused, remove along with other cmp code. hash, init, - convert=None, metadata=None, type=None, converter=None, kw_only=False, + eq=None, + order=None, ): + eq, order = _determine_eq_order(cmp, eq, order) + # Cache this descriptor here to speed things up later. bound_setattr = _obj_setattr.__get__(self, Attribute) # Despite the big red warning, people *do* instantiate `Attribute` # themselves. - if convert is not None: - if converter is not None: - raise RuntimeError( - "Can't pass both `convert` and `converter`. " - "Please use `converter` only." - ) - warnings.warn( - "The `convert` argument is deprecated in favor of `converter`." - " It will be removed after 2019/01.", - DeprecationWarning, - stacklevel=2, - ) - converter = convert - bound_setattr("name", name) bound_setattr("default", default) bound_setattr("validator", validator) bound_setattr("repr", repr) - bound_setattr("cmp", cmp) + bound_setattr("eq", eq) + bound_setattr("order", order) bound_setattr("hash", hash) bound_setattr("init", init) bound_setattr("converter", converter) @@ -1753,16 +1811,6 @@ class Attribute(object): def __setattr__(self, name, value): raise FrozenInstanceError() - @property - def convert(self): - warnings.warn( - "The `convert` attribute is deprecated in favor of `converter`. " - "It will be removed after 2019/01.", - DeprecationWarning, - stacklevel=2, - ) - return self.converter - @classmethod def from_counting_attr(cls, name, ca, type=None): # type holds the annotated value. deal with conflicts: @@ -1781,7 +1829,6 @@ class Attribute(object): "validator", "default", "type", - "convert", ) # exclude methods and deprecated alias } return cls( @@ -1789,9 +1836,19 @@ class Attribute(object): validator=ca._validator, default=ca._default, type=type, + cmp=None, **inst_dict ) + @property + def cmp(self): + """ + Simulate the presence of a cmp attribute and warn. + """ + warnings.warn(_CMP_DEPRECATION, DeprecationWarning, stacklevel=2) + + return self.eq and self.order + # Don't use attr.assoc since fields(Attribute) doesn't work def _assoc(self, **changes): """ @@ -1839,16 +1896,17 @@ _a = [ default=NOTHING, validator=None, repr=True, - cmp=True, + cmp=None, + eq=True, + order=False, hash=(name != "metadata"), init=True, ) for name in Attribute.__slots__ - if name != "convert" # XXX: remove once `convert` is gone ] Attribute = _add_hash( - _add_cmp(_add_repr(Attribute, attrs=_a), attrs=_a), + _add_eq(_add_repr(Attribute, attrs=_a), attrs=_a), attrs=[a for a in _a if a.hash], ) @@ -1866,7 +1924,8 @@ class _CountingAttr(object): "counter", "_default", "repr", - "cmp", + "eq", + "order", "hash", "init", "metadata", @@ -1881,22 +1940,34 @@ class _CountingAttr(object): default=NOTHING, validator=None, repr=True, - cmp=True, + cmp=None, hash=True, init=True, kw_only=False, + eq=True, + order=False, + ) + for name in ( + "counter", + "_default", + "repr", + "eq", + "order", + "hash", + "init", ) - for name in ("counter", "_default", "repr", "cmp", "hash", "init") ) + ( Attribute( name="metadata", default=None, validator=None, repr=True, - cmp=True, + cmp=None, hash=False, init=True, kw_only=False, + eq=True, + order=False, ), ) cls_counter = 0 @@ -1906,13 +1977,15 @@ class _CountingAttr(object): default, validator, repr, - cmp, + cmp, # XXX: unused, remove along with cmp hash, init, converter, metadata, type, kw_only, + eq, + order, ): _CountingAttr.cls_counter += 1 self.counter = _CountingAttr.cls_counter @@ -1923,7 +1996,8 @@ class _CountingAttr(object): else: self._validator = validator self.repr = repr - self.cmp = cmp + self.eq = eq + self.order = order self.hash = hash self.init = init self.converter = converter @@ -1963,7 +2037,7 @@ class _CountingAttr(object): return meth -_CountingAttr = _add_cmp(_add_repr(_CountingAttr)) +_CountingAttr = _add_eq(_add_repr(_CountingAttr)) @attrs(slots=True, init=False, hash=True) @@ -1971,7 +2045,7 @@ class Factory(object): """ Stores a factory callable. - If passed as the default value to :func:`attr.ib`, the factory is used to + If passed as the default value to `attr.ib`, the factory is used to generate a new value. :param callable factory: A callable that takes either none or exactly one @@ -2004,15 +2078,15 @@ def make_class(name, attrs, bases=(object,), **attributes_arguments): :param attrs: A list of names or a dictionary of mappings of names to attributes. - If *attrs* is a list or an ordered dict (:class:`dict` on Python 3.6+, - :class:`collections.OrderedDict` otherwise), the order is deduced from + If *attrs* is a list or an ordered dict (`dict` on Python 3.6+, + `collections.OrderedDict` otherwise), the order is deduced from the order of the names or attributes inside *attrs*. Otherwise the order of the definition of the attributes is used. - :type attrs: :class:`list` or :class:`dict` + :type attrs: `list` or `dict` :param tuple bases: Classes that the new class will subclass. - :param attributes_arguments: Passed unmodified to :func:`attr.s`. + :param attributes_arguments: Passed unmodified to `attr.s`. :return: A new class with *attrs*. :rtype: type @@ -2044,6 +2118,14 @@ def make_class(name, attrs, bases=(object,), **attributes_arguments): except (AttributeError, ValueError): pass + # We do it here for proper warnings with meaningful stacklevel. + cmp = attributes_arguments.pop("cmp", None) + attributes_arguments["eq"], attributes_arguments[ + "order" + ] = _determine_eq_order( + cmp, attributes_arguments.get("eq"), attributes_arguments.get("order") + ) + return _attrs(these=cls_dict, **attributes_arguments)(type_) diff --git a/third_party/python/attrs/attr/_version.py b/third_party/python/attrs/attr/_version.py new file mode 100644 index 000000000000..014e78a1b438 --- /dev/null +++ b/third_party/python/attrs/attr/_version.py @@ -0,0 +1,85 @@ +from __future__ import absolute_import, division, print_function + +from functools import total_ordering + +from ._funcs import astuple +from ._make import attrib, attrs + + +@total_ordering +@attrs(eq=False, order=False, slots=True, frozen=True) +class VersionInfo(object): + """ + A version object that can be compared to tuple of length 1--4: + + >>> attr.VersionInfo(19, 1, 0, "final") <= (19, 2) + True + >>> attr.VersionInfo(19, 1, 0, "final") < (19, 1, 1) + True + >>> vi = attr.VersionInfo(19, 2, 0, "final") + >>> vi < (19, 1, 1) + False + >>> vi < (19,) + False + >>> vi == (19, 2,) + True + >>> vi == (19, 2, 1) + False + + .. versionadded:: 19.2 + """ + + year = attrib(type=int) + minor = attrib(type=int) + micro = attrib(type=int) + releaselevel = attrib(type=str) + + @classmethod + def _from_version_string(cls, s): + """ + Parse *s* and return a _VersionInfo. + """ + v = s.split(".") + if len(v) == 3: + v.append("final") + + return cls( + year=int(v[0]), minor=int(v[1]), micro=int(v[2]), releaselevel=v[3] + ) + + def _ensure_tuple(self, other): + """ + Ensure *other* is a tuple of a valid length. + + Returns a possibly transformed *other* and ourselves as a tuple of + the same length as *other*. + """ + + if self.__class__ is other.__class__: + other = astuple(other) + + if not isinstance(other, tuple): + raise NotImplementedError + + if not (1 <= len(other) <= 4): + raise NotImplementedError + + return astuple(self)[: len(other)], other + + def __eq__(self, other): + try: + us, them = self._ensure_tuple(other) + except NotImplementedError: + return NotImplemented + + return us == them + + def __lt__(self, other): + try: + us, them = self._ensure_tuple(other) + except NotImplementedError: + return NotImplemented + + # Since alphabetically "dev0" < "final" < "post1" < "post2", we don't + # have to do anything special with releaselevel for now. + return us < them diff --git a/third_party/python/attrs/attr/_version.pyi b/third_party/python/attrs/attr/_version.pyi new file mode 100644 index 000000000000..45ced0863377 --- /dev/null +++ b/third_party/python/attrs/attr/_version.pyi @@ -0,0 +1,9 @@ +class VersionInfo: + @property + def year(self) -> int: ... + @property + def minor(self) -> int: ... + @property + def micro(self) -> int: ... + @property + def releaselevel(self) -> str: ... diff --git a/third_party/python/attrs/attr/converters.py b/third_party/python/attrs/attr/converters.py index 37c4a07a061e..859289784784 100644 --- a/third_party/python/attrs/attr/converters.py +++ b/third_party/python/attrs/attr/converters.py @@ -32,14 +32,14 @@ def default_if_none(default=NOTHING, factory=None): result of *factory*. :param default: Value to be used if ``None`` is passed. Passing an instance - of :class:`attr.Factory` is supported, however the ``takes_self`` option + of `attr.Factory` is supported, however the ``takes_self`` option is *not*. :param callable factory: A callable that takes not parameters whose result is used if ``None`` is passed. :raises TypeError: If **neither** *default* or *factory* is passed. :raises TypeError: If **both** *default* and *factory* are passed. - :raises ValueError: If an instance of :class:`attr.Factory` is passed with + :raises ValueError: If an instance of `attr.Factory` is passed with ``takes_self=True``. .. versionadded:: 18.2.0 diff --git a/third_party/python/attrs/attr/exceptions.py b/third_party/python/attrs/attr/exceptions.py index b12e41e97ae9..d1b76185c966 100644 --- a/third_party/python/attrs/attr/exceptions.py +++ b/third_party/python/attrs/attr/exceptions.py @@ -6,7 +6,7 @@ class FrozenInstanceError(AttributeError): A frozen/immutable instance has been attempted to be modified. It mirrors the behavior of ``namedtuples`` by using the same error message - and subclassing :exc:`AttributeError`. + and subclassing `AttributeError`. .. versionadded:: 16.1.0 """ @@ -55,3 +55,20 @@ class PythonTooOldError(RuntimeError): .. versionadded:: 18.2.0 """ + + +class NotCallableError(TypeError): + """ + A ``attr.ib()`` requiring a callable has been set with a value + that is not callable. + + .. versionadded:: 19.2.0 + """ + + def __init__(self, msg, value): + super(TypeError, self).__init__(msg, value) + self.msg = msg + self.value = value + + def __str__(self): + return str(self.msg) diff --git a/third_party/python/attrs/attr/exceptions.pyi b/third_party/python/attrs/attr/exceptions.pyi index 48fffcc1e2df..736fde2e1d20 100644 --- a/third_party/python/attrs/attr/exceptions.pyi +++ b/third_party/python/attrs/attr/exceptions.pyi @@ -1,3 +1,5 @@ +from typing import Any + class FrozenInstanceError(AttributeError): msg: str = ... @@ -5,3 +7,9 @@ class AttrsAttributeNotFoundError(ValueError): ... class NotAnAttrsClassError(ValueError): ... class DefaultAlreadySetError(RuntimeError): ... class UnannotatedAttributeError(RuntimeError): ... +class PythonTooOldError(RuntimeError): ... + +class NotCallableError(TypeError): + msg: str = ... + value: Any = ... + def __init__(self, msg: str, value: Any) -> None: ... diff --git a/third_party/python/attrs/attr/filters.py b/third_party/python/attrs/attr/filters.py index f1c69b8bac54..dc47e8fa38ce 100644 --- a/third_party/python/attrs/attr/filters.py +++ b/third_party/python/attrs/attr/filters.py @@ -1,5 +1,5 @@ """ -Commonly useful filters for :func:`attr.asdict`. +Commonly useful filters for `attr.asdict`. """ from __future__ import absolute_import, division, print_function @@ -23,9 +23,9 @@ def include(*what): Whitelist *what*. :param what: What to whitelist. - :type what: :class:`list` of :class:`type` or :class:`attr.Attribute`\\ s + :type what: `list` of `type` or `attr.Attribute`\\ s - :rtype: :class:`callable` + :rtype: `callable` """ cls, attrs = _split_what(what) @@ -40,9 +40,9 @@ def exclude(*what): Blacklist *what*. :param what: What to blacklist. - :type what: :class:`list` of classes or :class:`attr.Attribute`\\ s. + :type what: `list` of classes or `attr.Attribute`\\ s. - :rtype: :class:`callable` + :rtype: `callable` """ cls, attrs = _split_what(what) diff --git a/third_party/python/attrs/attr/validators.py b/third_party/python/attrs/attr/validators.py index 7fc4446be44a..839d310c3881 100644 --- a/third_party/python/attrs/attr/validators.py +++ b/third_party/python/attrs/attr/validators.py @@ -4,10 +4,23 @@ Commonly useful validators. from __future__ import absolute_import, division, print_function +import re + from ._make import _AndValidator, and_, attrib, attrs +from .exceptions import NotCallableError -__all__ = ["and_", "in_", "instance_of", "optional", "provides"] +__all__ = [ + "and_", + "deep_iterable", + "deep_mapping", + "in_", + "instance_of", + "is_callable", + "matches_re", + "optional", + "provides", +] @attrs(repr=False, slots=True, hash=True) @@ -40,20 +53,92 @@ class _InstanceOfValidator(object): def instance_of(type): """ - A validator that raises a :exc:`TypeError` if the initializer is called + A validator that raises a `TypeError` if the initializer is called with a wrong type for this particular attribute (checks are performed using - :func:`isinstance` therefore it's also valid to pass a tuple of types). + `isinstance` therefore it's also valid to pass a tuple of types). :param type: The type to check for. :type type: type or tuple of types :raises TypeError: With a human readable error message, the attribute - (of type :class:`attr.Attribute`), the expected type, and the value it + (of type `attr.Attribute`), the expected type, and the value it got. """ return _InstanceOfValidator(type) +@attrs(repr=False, frozen=True) +class _MatchesReValidator(object): + regex = attrib() + flags = attrib() + match_func = attrib() + + def __call__(self, inst, attr, value): + """ + We use a callable class to be able to change the ``__repr__``. + """ + if not self.match_func(value): + raise ValueError( + "'{name}' must match regex {regex!r}" + " ({value!r} doesn't)".format( + name=attr.name, regex=self.regex.pattern, value=value + ), + attr, + self.regex, + value, + ) + + def __repr__(self): + return "".format( + regex=self.regex + ) + + +def matches_re(regex, flags=0, func=None): + r""" + A validator that raises `ValueError` if the initializer is called + with a string that doesn't match *regex*. + + :param str regex: a regex string to match against + :param int flags: flags that will be passed to the underlying re function + (default 0) + :param callable func: which underlying `re` function to call (options + are `re.fullmatch`, `re.search`, `re.match`, default + is ``None`` which means either `re.fullmatch` or an emulation of + it on Python 2). For performance reasons, they won't be used directly + but on a pre-`re.compile`\ ed pattern. + + .. versionadded:: 19.2.0 + """ + fullmatch = getattr(re, "fullmatch", None) + valid_funcs = (fullmatch, None, re.search, re.match) + if func not in valid_funcs: + raise ValueError( + "'func' must be one of %s." + % ( + ", ".join( + sorted( + e and e.__name__ or "None" for e in set(valid_funcs) + ) + ), + ) + ) + + pattern = re.compile(regex, flags) + if func is re.match: + match_func = pattern.match + elif func is re.search: + match_func = pattern.search + else: + if fullmatch: + match_func = pattern.fullmatch + else: + pattern = re.compile(r"(?:{})\Z".format(regex), flags) + match_func = pattern.match + + return _MatchesReValidator(pattern, flags, match_func) + + @attrs(repr=False, slots=True, hash=True) class _ProvidesValidator(object): interface = attrib() @@ -81,7 +166,7 @@ class _ProvidesValidator(object): def provides(interface): """ - A validator that raises a :exc:`TypeError` if the initializer is called + A validator that raises a `TypeError` if the initializer is called with an object that does not provide the requested *interface* (checks are performed using ``interface.providedBy(value)`` (see `zope.interface `_). @@ -89,7 +174,7 @@ def provides(interface): :param zope.interface.Interface interface: The interface to check for. :raises TypeError: With a human readable error message, the attribute - (of type :class:`attr.Attribute`), the expected interface, and the + (of type `attr.Attribute`), the expected interface, and the value it got. """ return _ProvidesValidator(interface) @@ -119,7 +204,7 @@ def optional(validator): :param validator: A validator (or a list of validators) that is used for non-``None`` values. - :type validator: callable or :class:`list` of callables. + :type validator: callable or `list` of callables. .. versionadded:: 15.1.0 .. versionchanged:: 17.1.0 *validator* can be a list of validators. @@ -154,15 +239,15 @@ class _InValidator(object): def in_(options): """ - A validator that raises a :exc:`ValueError` if the initializer is called + A validator that raises a `ValueError` if the initializer is called with a value that does not belong in the options provided. The check is performed using ``value in options``. :param options: Allowed options. - :type options: list, tuple, :class:`enum.Enum`, ... + :type options: list, tuple, `enum.Enum`, ... :raises ValueError: With a human readable error message, the attribute (of - type :class:`attr.Attribute`), the expected options, and the value it + type `attr.Attribute`), the expected options, and the value it got. .. versionadded:: 17.1.0 @@ -177,7 +262,16 @@ class _IsCallableValidator(object): We use a callable class to be able to change the ``__repr__``. """ if not callable(value): - raise TypeError("'{name}' must be callable".format(name=attr.name)) + message = ( + "'{name}' must be callable " + "(got {value!r} that is a {actual!r})." + ) + raise NotCallableError( + msg=message.format( + name=attr.name, value=value, actual=value.__class__ + ), + value=value, + ) def __repr__(self): return "" @@ -185,13 +279,15 @@ class _IsCallableValidator(object): def is_callable(): """ - A validator that raises a :class:`TypeError` if the initializer is called - with a value for this particular attribute that is not callable. + A validator that raises a `attr.exceptions.NotCallableError` if the + initializer is called with a value for this particular attribute + that is not callable. .. versionadded:: 19.1.0 - :raises TypeError: With a human readable error message containing the - attribute (of type :class:`attr.Attribute`) name. + :raises `attr.exceptions.NotCallableError`: With a human readable error + message containing the attribute (`attr.Attribute`) name, + and the value it got. """ return _IsCallableValidator() diff --git a/third_party/python/attrs/attr/validators.pyi b/third_party/python/attrs/attr/validators.pyi index 01af06845ec3..9a22abb197d7 100644 --- a/third_party/python/attrs/attr/validators.pyi +++ b/third_party/python/attrs/attr/validators.pyi @@ -1,24 +1,66 @@ -from typing import Container, List, Union, TypeVar, Type, Any, Optional, Tuple +from typing import ( + Container, + List, + Union, + TypeVar, + Type, + Any, + Optional, + Tuple, + Iterable, + Mapping, + Callable, + Match, + AnyStr, + overload, +) from . import _ValidatorType _T = TypeVar("_T") +_T1 = TypeVar("_T1") +_T2 = TypeVar("_T2") +_T3 = TypeVar("_T3") +_I = TypeVar("_I", bound=Iterable) +_K = TypeVar("_K") +_V = TypeVar("_V") +_M = TypeVar("_M", bound=Mapping) +# To be more precise on instance_of use some overloads. +# If there are more than 3 items in the tuple then we fall back to Any +@overload +def instance_of(type: Type[_T]) -> _ValidatorType[_T]: ... +@overload +def instance_of(type: Tuple[Type[_T]]) -> _ValidatorType[_T]: ... +@overload def instance_of( - type: Union[Tuple[Type[_T], ...], Type[_T]] -) -> _ValidatorType[_T]: ... + type: Tuple[Type[_T1], Type[_T2]] +) -> _ValidatorType[Union[_T1, _T2]]: ... +@overload +def instance_of( + type: Tuple[Type[_T1], Type[_T2], Type[_T3]] +) -> _ValidatorType[Union[_T1, _T2, _T3]]: ... +@overload +def instance_of(type: Tuple[type, ...]) -> _ValidatorType[Any]: ... def provides(interface: Any) -> _ValidatorType[Any]: ... def optional( validator: Union[_ValidatorType[_T], List[_ValidatorType[_T]]] ) -> _ValidatorType[Optional[_T]]: ... def in_(options: Container[_T]) -> _ValidatorType[_T]: ... def and_(*validators: _ValidatorType[_T]) -> _ValidatorType[_T]: ... +def matches_re( + regex: AnyStr, + flags: int = ..., + func: Optional[ + Callable[[AnyStr, AnyStr, int], Optional[Match[AnyStr]]] + ] = ..., +) -> _ValidatorType[AnyStr]: ... def deep_iterable( member_validator: _ValidatorType[_T], - iterable_validator: Optional[_ValidatorType[_T]], -) -> _ValidatorType[_T]: ... + iterable_validator: Optional[_ValidatorType[_I]] = ..., +) -> _ValidatorType[_I]: ... def deep_mapping( - key_validator: _ValidatorType[_T], - value_validator: _ValidatorType[_T], - mapping_validator: Optional[_ValidatorType[_T]], -) -> _ValidatorType[_T]: ... + key_validator: _ValidatorType[_K], + value_validator: _ValidatorType[_V], + mapping_validator: Optional[_ValidatorType[_M]] = ..., +) -> _ValidatorType[_M]: ... def is_callable() -> _ValidatorType[_T]: ... diff --git a/third_party/python/attrs/attrs-19.1.0.dist-info/RECORD b/third_party/python/attrs/attrs-19.1.0.dist-info/RECORD deleted file mode 100644 index 034b3cbff804..000000000000 --- a/third_party/python/attrs/attrs-19.1.0.dist-info/RECORD +++ /dev/null @@ -1,20 +0,0 @@ -attr/__init__.py,sha256=3XomfUfit8bVVEmSf1bRhLnRMPKauPbzFqPUnVRPgXw,1244 -attr/__init__.pyi,sha256=OON4rNWdgL69frd_WdrxtuQe8CEczl3aFpgifFeESN8,7769 -attr/_compat.py,sha256=GcjqWHrwUWGVCbDKY7twYt-Rr_4nPJqBnfrf5SeHsIY,4583 -attr/_config.py,sha256=_KvW0mQdH2PYjHc0YfIUaV_o2pVfM7ziMEYTxwmEhOA,514 -attr/_funcs.py,sha256=7v3MNMHdOUP2NkiLPwEiWAorBs3uNQq5Rn70Odr5uqo,9725 -attr/_make.py,sha256=be1PmzR8EDGfVA2Cx6ljsTIuXRxW2tEWPpTqtQXde0Y,68317 -attr/converters.py,sha256=SFPiz6-hAs2pw3kn7SzkBcdpE9AjW8iT9wjpe2eLDrQ,2155 -attr/converters.pyi,sha256=wAhCoOT1MFV8t323rpD87O7bxQ8CYLTPiBQd-29BieI,351 -attr/exceptions.py,sha256=N0WQfKvBVd4GWgDxTbFScg4ajy7-HlyvXiwlSQBA0jA,1272 -attr/exceptions.pyi,sha256=sq7TbBEGGSf81uFXScW9_aO62vd0v6LAvqz0a8Hrsxw,257 -attr/filters.py,sha256=s6NrcRWJKlCQauPEH0S4lmgFwlCdUQcHKcNkDHpptN4,1153 -attr/filters.pyi,sha256=xDpmKQlFdssgxGa5tsl1ADh_3zwAwAT4vUhd8h-8-Tk,214 -attr/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 -attr/validators.py,sha256=ZAf_y5wNHyq2Rdlin_fwplQnU2u5wZnvmYJq1JddPtM,8750 -attr/validators.pyi,sha256=p2xr2ob8RaKW3PqlKDrQQVAyl8ZH4pNdlZzWXapGPjk,897 -attrs-19.1.0.dist-info/LICENSE,sha256=v2WaKLSSQGAvVrvfSQy-LsUJsVuY-Z17GaUsdA4yeGM,1082 -attrs-19.1.0.dist-info/METADATA,sha256=5yXp3BTFGRkY2hQDs18h-2dT7xnSlExRUfxvujCtHTE,10275 -attrs-19.1.0.dist-info/WHEEL,sha256=_wJFdOYk7i3xxT8ElOkUJvOdOvfNGbR9g-bf6UQT6sU,110 -attrs-19.1.0.dist-info/top_level.txt,sha256=tlRYMddkRlKPqJ96wP2_j9uEsmcNHgD2SbuWd4CzGVU,5 -attrs-19.1.0.dist-info/RECORD,, diff --git a/third_party/python/attrs/attrs-19.1.0.dist-info/LICENSE b/third_party/python/attrs/attrs-19.2.0.dist-info/LICENSE similarity index 100% rename from third_party/python/attrs/attrs-19.1.0.dist-info/LICENSE rename to third_party/python/attrs/attrs-19.2.0.dist-info/LICENSE diff --git a/third_party/python/attrs/attrs-19.1.0.dist-info/METADATA b/third_party/python/attrs/attrs-19.2.0.dist-info/METADATA similarity index 60% rename from third_party/python/attrs/attrs-19.1.0.dist-info/METADATA rename to third_party/python/attrs/attrs-19.2.0.dist-info/METADATA index 81b6fc8cfbe1..3457b238259a 100644 --- a/third_party/python/attrs/attrs-19.1.0.dist-info/METADATA +++ b/third_party/python/attrs/attrs-19.2.0.dist-info/METADATA @@ -1,6 +1,6 @@ Metadata-Version: 2.1 Name: attrs -Version: 19.1.0 +Version: 19.2.0 Summary: Classes Without Boilerplate Home-page: https://www.attrs.org/ Author: Hynek Schlawack @@ -30,11 +30,20 @@ Classifier: Programming Language :: Python :: Implementation :: CPython Classifier: Programming Language :: Python :: Implementation :: PyPy Classifier: Topic :: Software Development :: Libraries :: Python Modules Requires-Python: >=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.* +Description-Content-Type: text/x-rst +Provides-Extra: azure-pipelines +Requires-Dist: coverage ; extra == 'azure-pipelines' +Requires-Dist: hypothesis ; extra == 'azure-pipelines' +Requires-Dist: pympler ; extra == 'azure-pipelines' +Requires-Dist: pytest (>=4.3.0) ; extra == 'azure-pipelines' +Requires-Dist: six ; extra == 'azure-pipelines' +Requires-Dist: zope.interface ; extra == 'azure-pipelines' +Requires-Dist: pytest-azurepipelines ; extra == 'azure-pipelines' Provides-Extra: dev Requires-Dist: coverage ; extra == 'dev' Requires-Dist: hypothesis ; extra == 'dev' Requires-Dist: pympler ; extra == 'dev' -Requires-Dist: pytest ; extra == 'dev' +Requires-Dist: pytest (>=4.3.0) ; extra == 'dev' Requires-Dist: six ; extra == 'dev' Requires-Dist: zope.interface ; extra == 'dev' Requires-Dist: sphinx ; extra == 'dev' @@ -46,7 +55,7 @@ Provides-Extra: tests Requires-Dist: coverage ; extra == 'tests' Requires-Dist: hypothesis ; extra == 'tests' Requires-Dist: pympler ; extra == 'tests' -Requires-Dist: pytest ; extra == 'tests' +Requires-Dist: pytest (>=4.3.0) ; extra == 'tests' Requires-Dist: six ; extra == 'tests' Requires-Dist: zope.interface ; extra == 'tests' @@ -61,8 +70,8 @@ Requires-Dist: zope.interface ; extra == 'tests' :target: https://www.attrs.org/en/stable/?badge=stable :alt: Documentation Status -.. image:: https://travis-ci.org/python-attrs/attrs.svg?branch=master - :target: https://travis-ci.org/python-attrs/attrs +.. image:: https://attrs.visualstudio.com/attrs/_apis/build/status/python-attrs.attrs?branchName=master + :target: https://attrs.visualstudio.com/attrs/_build/latest?definitionId=1&branchName=master :alt: CI Status .. image:: https://codecov.io/github/python-attrs/attrs/branch/master/graph/badge.svg @@ -70,7 +79,7 @@ Requires-Dist: zope.interface ; extra == 'tests' :alt: Test Coverage .. image:: https://img.shields.io/badge/code%20style-black-000000.svg - :target: https://github.com/ambv/black + :target: https://github.com/psf/black :alt: Code style: black .. teaser-begin @@ -124,7 +133,7 @@ After *declaring* your attributes ``attrs`` gives you: - a concise and explicit overview of the class's attributes, - a nice human-readable ``__repr__``, -- a complete set of comparison methods, +- a complete set of comparison methods (equality and ordering), - an initializer, - and much more, @@ -153,12 +162,12 @@ Testimonials It exerts a subtle, but positive, design influence in all the codebases I’ve see it used in. -**Kenneth Reitz**, author of `Requests `_ and Developer Advocate at DigitalOcean, (`on paper no less `_!): +**Kenneth Reitz**, creator of `Requests `_ (`on paper no less `_!): attrs—classes for humans. I like it. -**Łukasz Langa**, prolific CPython core developer and Production Engineer at Facebook: +**Łukasz Langa**, creator of `Black `_, prolific Python core developer, and release manager for Python 3.8 and 3.9: I'm increasingly digging your attr.ocity. Good job! @@ -193,44 +202,70 @@ If you'd like to contribute to ``attrs`` you're most welcome and we've written ` Release Information =================== -19.1.0 (2019-03-03) +19.2.0 (2019-10-01) ------------------- Backward-incompatible Changes ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -- Fixed a bug where deserialized objects with ``cache_hash=True`` could have incorrect hash code values. - This change breaks classes with ``cache_hash=True`` when a custom ``__setstate__`` is present. - An exception will be thrown when applying the ``attrs`` annotation to such a class. - This limitation is tracked in issue `#494 `_. - `#482 `_ +- Removed deprecated ``Attribute`` attribute ``convert`` per scheduled removal on 2019/1. + This planned deprecation is tracked in issue `#307 `_. + `#504 `_ +- ``__lt__``, ``__le__``, ``__gt__``, and ``__ge__`` do not consider subclasses comparable anymore. + + This has been deprecated since 18.2.0 and was raising a ``DeprecationWarning`` for over a year. + `#570 `_ + + +Deprecations +^^^^^^^^^^^^ + +- The ``cmp`` argument to ``attr.s()`` and ``attr.ib()`` is now deprecated. + + Please use ``eq`` to add equality methods (``__eq__`` and ``__ne__``) and ``order`` to add ordering methods (``__lt__``, ``__le__``, ``__gt__``, and ``__ge__``) instead – just like with `dataclasses `_. + + Both are effectively ``True`` by default but it's enough to set ``eq=False`` to disable both at once. + Passing ``eq=False, order=True`` explicitly will raise a ``ValueError`` though. + + Since this is arguably a deeper backward-compatibility break, it will have an extended deprecation period until 2021-06-01. + After that day, the ``cmp`` argument will be removed. + + ``attr.Attribute`` also isn't orderable anymore. + `#574 `_ Changes ^^^^^^^ -- Add ``is_callable``, ``deep_iterable``, and ``deep_mapping`` validators. +- Updated ``attr.validators.__all__`` to include new validators added in `#425`_. + `#517 `_ +- Slotted classes now use a pure Python mechanism to rewrite the ``__class__`` cell when rebuilding the class, so ``super()`` works even on environments where ``ctypes`` is not installed. + `#522 `_ +- When collecting attributes using ``@attr.s(auto_attribs=True)``, attributes with a default of ``None`` are now deleted too. + `#523 `_, + `#556 `_ +- Fixed ``attr.validators.deep_iterable()`` and ``attr.validators.deep_mapping()`` type stubs. + `#533 `_ +- ``attr.validators.is_callable()`` validator now raises an exception ``attr.exceptions.NotCallableError``, a subclass of ``TypeError``, informing the received value. + `#536 `_ +- ``@attr.s(auto_exc=True)`` now generates classes that are hashable by ID, as the documentation always claimed it would. + `#543 `_, + `#563 `_ +- Added ``attr.validators.matches_re()`` that checks string attributes whether they match a regular expression. + `#552 `_ +- Keyword-only attributes (``kw_only=True``) and attributes that are excluded from the ``attrs``'s ``__init__`` (``init=False``) now can appear before mandatory attributes. + `#559 `_ +- The fake filename for generated methods is now more stable. + It won't change when you restart the process. + `#560 `_ +- The value passed to ``@attr.ib(repr=…)`` can now be either a boolean (as before) or a callable. + That callable must return a string and is then used for formatting the attribute by the generated ``__repr__()`` method. + `#568 `_ +- Added ``attr.__version_info__`` that can be used to reliably check the version of ``attrs`` and write forward- and backward-compatible code. + Please check out the `section on deprecated APIs `_ on how to use it. + `#580 `_ - * ``is_callable``: validates that a value is callable - * ``deep_iterable``: Allows recursion down into an iterable, - applying another validator to every member in the iterable - as well as applying an optional validator to the iterable itself. - * ``deep_mapping``: Allows recursion down into the items in a mapping object, - applying a key validator and a value validator to the key and value in every item. - Also applies an optional validator to the mapping object itself. - - You can find them in the ``attr.validators`` package. - `#425 `_ -- Fixed stub files to prevent errors raised by mypy's ``disallow_any_generics = True`` option. - `#443 `_ -- Attributes with ``init=False`` now can follow after ``kw_only=True`` attributes. - `#450 `_ -- ``attrs`` now has first class support for defining exception classes. - - If you define a class using ``@attr.s(auto_exc=True)`` and subclass an exception, the class will behave like a well-behaved exception class including an appropriate ``__str__`` method, and all attributes additionally available in an ``args`` attribute. - `#500 `_ -- Clarified documentation for hashing to warn that hashable objects should be deeply immutable (in their usage, even if this is not enforced). - `#503 `_ + .. _`#425`: https://github.com/python-attrs/attrs/issues/425 `Full changelog `_. diff --git a/third_party/python/attrs/attrs-19.2.0.dist-info/RECORD b/third_party/python/attrs/attrs-19.2.0.dist-info/RECORD new file mode 100644 index 000000000000..4a08ba037e3a --- /dev/null +++ b/third_party/python/attrs/attrs-19.2.0.dist-info/RECORD @@ -0,0 +1,22 @@ +attr/__init__.py,sha256=nRvEecOWLaJsMraOK89f4hMYThTUZHWj1B0jl249M-0,1344 +attr/__init__.pyi,sha256=5AVtEEzK-g3HO1SUll44hTL8LFoM8TYD7Gn9vEMFGzk,8252 +attr/_compat.py,sha256=-pJtdtqgCg0K6rH_BWf3wKuTum58GD-WWPclQQ2SUaU,7326 +attr/_config.py,sha256=_KvW0mQdH2PYjHc0YfIUaV_o2pVfM7ziMEYTxwmEhOA,514 +attr/_funcs.py,sha256=unAJfNGSTOzxyFzkj7Rs3O1bfsQodmXyir9uZKen-vY,9696 +attr/_make.py,sha256=4pdTus8d4OkitzlwytTPP7TNLZK6pVIoKg6KdAZMwYQ,70804 +attr/_version.py,sha256=azMi1lNelb3cJvvYUMXsXVbUANkRzbD5IEiaXVpeVr4,2162 +attr/_version.pyi,sha256=x_M3L3WuB7r_ULXAWjx959udKQ4HLB8l-hsc1FDGNvk,209 +attr/converters.py,sha256=5QJRYSXE8G7PW0289y_SPwvvZIcw-nJIuBlfYVdB4BQ,2141 +attr/converters.pyi,sha256=wAhCoOT1MFV8t323rpD87O7bxQ8CYLTPiBQd-29BieI,351 +attr/exceptions.py,sha256=hbhOa3b4W8_mRrbj3FsMTR4Bt5xzbJs5xaFTWn8s6h4,1635 +attr/exceptions.pyi,sha256=4zuaJyl2axxWbqnZgxo_2oTpPNbyowEw3A4hqV5PmAc,458 +attr/filters.py,sha256=weDxwATsa69T_0bPVjiM1fGsciAMQmwhY5G8Jm5BxuI,1098 +attr/filters.pyi,sha256=xDpmKQlFdssgxGa5tsl1ADh_3zwAwAT4vUhd8h-8-Tk,214 +attr/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +attr/validators.py,sha256=8AsxgdDgh3sGPseiUIMPGcTr6PvaDYfH3AK46tsvs8U,11460 +attr/validators.pyi,sha256=vZgsJqUwrJevh4v_Hd7_RSXqDrBctE6-3AEZ7uYKodo,1868 +attrs-19.2.0.dist-info/LICENSE,sha256=v2WaKLSSQGAvVrvfSQy-LsUJsVuY-Z17GaUsdA4yeGM,1082 +attrs-19.2.0.dist-info/METADATA,sha256=qPqvhqvovqyvpsQebMPTXsOi8pv2xuzUDkUzAxz-wvM,12750 +attrs-19.2.0.dist-info/WHEEL,sha256=8zNYZbwQSXoB9IfXOjPfeNwvAsALAjffgk27FqvCWbo,110 +attrs-19.2.0.dist-info/top_level.txt,sha256=tlRYMddkRlKPqJ96wP2_j9uEsmcNHgD2SbuWd4CzGVU,5 +attrs-19.2.0.dist-info/RECORD,, diff --git a/third_party/python/attrs/attrs-19.1.0.dist-info/WHEEL b/third_party/python/attrs/attrs-19.2.0.dist-info/WHEEL similarity index 70% rename from third_party/python/attrs/attrs-19.1.0.dist-info/WHEEL rename to third_party/python/attrs/attrs-19.2.0.dist-info/WHEEL index c4bde3037775..8b701e93c231 100644 --- a/third_party/python/attrs/attrs-19.1.0.dist-info/WHEEL +++ b/third_party/python/attrs/attrs-19.2.0.dist-info/WHEEL @@ -1,5 +1,5 @@ Wheel-Version: 1.0 -Generator: bdist_wheel (0.32.3) +Generator: bdist_wheel (0.33.6) Root-Is-Purelib: true Tag: py2-none-any Tag: py3-none-any diff --git a/third_party/python/attrs/attrs-19.1.0.dist-info/top_level.txt b/third_party/python/attrs/attrs-19.2.0.dist-info/top_level.txt similarity index 100% rename from third_party/python/attrs/attrs-19.1.0.dist-info/top_level.txt rename to third_party/python/attrs/attrs-19.2.0.dist-info/top_level.txt diff --git a/third_party/python/requirements.in b/third_party/python/requirements.in index 4099209f7c8a..8a37d9c55687 100644 --- a/third_party/python/requirements.in +++ b/third_party/python/requirements.in @@ -1,5 +1,5 @@ appdirs==1.4.4 -attrs==19.1.0 +attrs==19.2.0 blessings==1.7 cbor2==4.0.1 # Though we don't depend on colorama directly, we need to explicitly diff --git a/third_party/python/requirements.txt b/third_party/python/requirements.txt index 11b89d9ec1f8..ce246e1e6040 100644 --- a/third_party/python/requirements.txt +++ b/third_party/python/requirements.txt @@ -50,9 +50,9 @@ async-timeout==3.0.1 \ # via # aiohttp # taskcluster -attrs==19.1.0 \ - --hash=sha256:69c0dbf2ed392de1cb5ec704444b08a5ef81680a61cb899dc08127123af36a79 \ - --hash=sha256:f0b870f674851ecbfbbbd364d6b5cbdff9dcedbc7f3f5e18a6891057f21fe399 +attrs==19.2.0 \ + --hash=sha256:ec20e7a4825331c1b5ebf261d111e16fa9612c1f7a5e1f884f12bd53a664dfd2 \ + --hash=sha256:f913492e1663d3c36f502e5e9ba6cd13cf19d7fab50aa13239e420fef95e1396 # via # -r requirements-mach-vendor-python.in # aiohttp diff --git a/tools/lint/tox/tox_requirements.txt b/tools/lint/tox/tox_requirements.txt index b00faaf803d0..4ec41914436a 100644 --- a/tools/lint/tox/tox_requirements.txt +++ b/tools/lint/tox/tox_requirements.txt @@ -1,4 +1,7 @@ -pluggy==0.6.0 --hash=sha256:e160a7fcf25762bb60efc7e171d4497ff1d8d2d75a3d0df7a21b76821ecbf5c5 +pluggy==0.13.1 --hash=sha256:966c145cd83c96502c3c3868f50408687b38434af77734af1e9ca461a4081d2d +importlib-metadata==0.23 --hash=sha256:d5f18a79777f3aa179c145737780282e27b508fc8fd688cb17c7a813e8bd39af +more-itertools==7.2.0 --hash=sha256:92b8c4b06dac4f0611c0729b2f2ede52b2e1bac1ab48f089c7ddc12e26bb60c4 +zipp==0.6.0 --hash=sha256:f06903e9f1f43b12d371004b4ac7b06ab39a44adc747266928ae6debfa7b3335 py==1.5.4 --hash=sha256:e31fb2767eb657cbde86c454f02e99cb846d3cd9d61b318525140214fdc0e98e tox==2.7.0 --hash=sha256:0f37ea637ead4a5bbae91531b0bf8fd327c7152e20255e5960ee180598228d21 virtualenv==15.1.0 --hash=sha256:39d88b533b422825d644087a21e78c45cf5af0ef7a99a1fc9fbb7b481e5c85b0