Mercurial > hg
changeset 34397:765eb17a7eb8
thirdparty: vendor attrs
The attrs package allows defining namedtuple-like classes with no weird
behavior and no runtime performance cost.
This patch vendors in attrs 17.2.0.
# no-check-commit
Differential Revision: https://phab.mercurial-scm.org/D867
author | Siddharth Agarwal <sid0@fb.com> |
---|---|
date | Sun, 01 Oct 2017 04:14:16 -0700 |
parents | 9fb9f8440b71 |
children | e51c8ffa1ffa |
files | mercurial/thirdparty/__init__.py mercurial/thirdparty/attr/LICENSE.txt mercurial/thirdparty/attr/__init__.py mercurial/thirdparty/attr/_compat.py mercurial/thirdparty/attr/_config.py mercurial/thirdparty/attr/_funcs.py mercurial/thirdparty/attr/_make.py mercurial/thirdparty/attr/converters.py mercurial/thirdparty/attr/exceptions.py mercurial/thirdparty/attr/filters.py mercurial/thirdparty/attr/validators.py setup.py |
diffstat | 11 files changed, 1759 insertions(+), 0 deletions(-) [+] |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mercurial/thirdparty/attr/LICENSE.txt Sun Oct 01 04:14:16 2017 -0700 @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2015 Hynek Schlawack + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE.
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mercurial/thirdparty/attr/__init__.py Sun Oct 01 04:14:16 2017 -0700 @@ -0,0 +1,71 @@ +from __future__ import absolute_import, division, print_function + +from ._funcs import ( + asdict, + assoc, + astuple, + evolve, + has, +) +from ._make import ( + Attribute, + Factory, + NOTHING, + attr, + attributes, + fields, + make_class, + validate, +) +from ._config import ( + get_run_validators, + set_run_validators, +) +from . import exceptions +from . import filters +from . import converters +from . import validators + + +__version__ = "17.2.0" + +__title__ = "attrs" +__description__ = "Classes Without Boilerplate" +__uri__ = "http://www.attrs.org/" +__doc__ = __description__ + " <" + __uri__ + ">" + +__author__ = "Hynek Schlawack" +__email__ = "hs@ox.cx" + +__license__ = "MIT" +__copyright__ = "Copyright (c) 2015 Hynek Schlawack" + + +s = attrs = attributes +ib = attrib = attr + +__all__ = [ + "Attribute", + "Factory", + "NOTHING", + "asdict", + "assoc", + "astuple", + "attr", + "attrib", + "attributes", + "attrs", + "converters", + "evolve", + "exceptions", + "fields", + "filters", + "get_run_validators", + "has", + "ib", + "make_class", + "s", + "set_run_validators", + "validate", + "validators", +]
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mercurial/thirdparty/attr/_compat.py Sun Oct 01 04:14:16 2017 -0700 @@ -0,0 +1,90 @@ +from __future__ import absolute_import, division, print_function + +import sys +import types + + +PY2 = sys.version_info[0] == 2 + + +if PY2: + from UserDict import IterableUserDict + + # We 'bundle' isclass instead of using inspect as importing inspect is + # fairly expensive (order of 10-15 ms for a modern machine in 2016) + def isclass(klass): + return isinstance(klass, (type, types.ClassType)) + + # TYPE is used in exceptions, repr(int) is different on Python 2 and 3. + TYPE = "type" + + def iteritems(d): + return d.iteritems() + + def iterkeys(d): + return d.iterkeys() + + # Python 2 is bereft of a read-only dict proxy, so we make one! + class ReadOnlyDict(IterableUserDict): + """ + Best-effort read-only dict wrapper. + """ + + def __setitem__(self, key, val): + # We gently pretend we're a Python 3 mappingproxy. + raise TypeError("'mappingproxy' object does not support item " + "assignment") + + def update(self, _): + # We gently pretend we're a Python 3 mappingproxy. + raise AttributeError("'mappingproxy' object has no attribute " + "'update'") + + def __delitem__(self, _): + # We gently pretend we're a Python 3 mappingproxy. + raise TypeError("'mappingproxy' object does not support item " + "deletion") + + def clear(self): + # We gently pretend we're a Python 3 mappingproxy. + raise AttributeError("'mappingproxy' object has no attribute " + "'clear'") + + def pop(self, key, default=None): + # We gently pretend we're a Python 3 mappingproxy. + raise AttributeError("'mappingproxy' object has no attribute " + "'pop'") + + def popitem(self): + # We gently pretend we're a Python 3 mappingproxy. + raise AttributeError("'mappingproxy' object has no attribute " + "'popitem'") + + def setdefault(self, key, default=None): + # We gently pretend we're a Python 3 mappingproxy. + raise AttributeError("'mappingproxy' object has no attribute " + "'setdefault'") + + def __repr__(self): + # Override to be identical to the Python 3 version. + return "mappingproxy(" + repr(self.data) + ")" + + def metadata_proxy(d): + res = ReadOnlyDict() + res.data.update(d) # We blocked update, so we have to do it like this. + return res + +else: + def isclass(klass): + return isinstance(klass, type) + + TYPE = "class" + + def iteritems(d): + return d.items() + + def iterkeys(d): + return d.keys() + + def metadata_proxy(d): + return types.MappingProxyType(dict(d))
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mercurial/thirdparty/attr/_config.py Sun Oct 01 04:14:16 2017 -0700 @@ -0,0 +1,23 @@ +from __future__ import absolute_import, division, print_function + + +__all__ = ["set_run_validators", "get_run_validators"] + +_run_validators = True + + +def set_run_validators(run): + """ + Set whether or not validators are run. By default, they are run. + """ + if not isinstance(run, bool): + raise TypeError("'run' must be bool.") + global _run_validators + _run_validators = run + + +def get_run_validators(): + """ + Return whether or not validators are run. + """ + return _run_validators
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mercurial/thirdparty/attr/_funcs.py Sun Oct 01 04:14:16 2017 -0700 @@ -0,0 +1,212 @@ +from __future__ import absolute_import, division, print_function + +import copy + +from ._compat import iteritems +from ._make import NOTHING, fields, _obj_setattr +from .exceptions import AttrsAttributeNotFoundError + + +def asdict(inst, recurse=True, filter=None, dict_factory=dict, + retain_collection_types=False): + """ + Return the ``attrs`` attribute values of *inst* as a dict. + + Optionally recurse into other ``attrs``-decorated classes. + + :param inst: Instance of an ``attrs``-decorated class. + :param bool recurse: Recurse into classes that are also + ``attrs``-decorated. + :param callable filter: A callable whose return code deteremines whether an + attribute or element is included (``True``) or dropped (``False``). Is + called with the :class:`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 + dictionaries, pass in ``collections.OrderedDict``. + :param bool retain_collection_types: Do not convert to ``list`` when + encountering an attribute whose type is ``tuple`` or ``set``. Only + meaningful if ``recurse`` is ``True``. + + :rtype: return type of *dict_factory* + + :raise attr.exceptions.NotAnAttrsClassError: If *cls* is not an ``attrs`` + class. + + .. versionadded:: 16.0.0 *dict_factory* + .. versionadded:: 16.1.0 *retain_collection_types* + """ + attrs = fields(inst.__class__) + rv = dict_factory() + for a in attrs: + v = getattr(inst, a.name) + if filter is not None and not filter(a, v): + continue + if recurse is True: + if has(v.__class__): + rv[a.name] = asdict(v, recurse=True, filter=filter, + dict_factory=dict_factory) + elif isinstance(v, (tuple, list, set)): + cf = v.__class__ if retain_collection_types is True else list + rv[a.name] = cf([ + asdict(i, recurse=True, filter=filter, + dict_factory=dict_factory) + if has(i.__class__) else i + for i in v + ]) + elif isinstance(v, dict): + df = dict_factory + rv[a.name] = df(( + asdict(kk, dict_factory=df) if has(kk.__class__) else kk, + asdict(vv, dict_factory=df) if has(vv.__class__) else vv) + for kk, vv in iteritems(v)) + else: + rv[a.name] = v + else: + rv[a.name] = v + return rv + + +def astuple(inst, recurse=True, filter=None, tuple_factory=tuple, + retain_collection_types=False): + """ + Return the ``attrs`` attribute values of *inst* as a tuple. + + Optionally recurse into other ``attrs``-decorated classes. + + :param inst: Instance of an ``attrs``-decorated class. + :param bool recurse: Recurse into classes that are also + ``attrs``-decorated. + :param callable filter: A callable whose return code determines whether an + attribute or element is included (``True``) or dropped (``False``). Is + called with the :class:`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. + :param bool retain_collection_types: Do not convert to ``list`` + or ``dict`` when encountering an attribute which type is + ``tuple``, ``dict`` or ``set``. Only meaningful if ``recurse`` is + ``True``. + + :rtype: return type of *tuple_factory* + + :raise attr.exceptions.NotAnAttrsClassError: If *cls* is not an ``attrs`` + class. + + .. versionadded:: 16.2.0 + """ + attrs = fields(inst.__class__) + rv = [] + retain = retain_collection_types # Very long. :/ + for a in attrs: + v = getattr(inst, a.name) + if filter is not None and not filter(a, v): + continue + if recurse is True: + if has(v.__class__): + rv.append(astuple(v, recurse=True, filter=filter, + tuple_factory=tuple_factory, + retain_collection_types=retain)) + elif isinstance(v, (tuple, list, set)): + cf = v.__class__ if retain is True else list + rv.append(cf([ + astuple(j, recurse=True, filter=filter, + tuple_factory=tuple_factory, + retain_collection_types=retain) + if has(j.__class__) else j + for j in v + ])) + elif isinstance(v, dict): + df = v.__class__ if retain is True else dict + rv.append(df( + ( + astuple( + kk, + tuple_factory=tuple_factory, + retain_collection_types=retain + ) if has(kk.__class__) else kk, + astuple( + vv, + tuple_factory=tuple_factory, + retain_collection_types=retain + ) if has(vv.__class__) else vv + ) + for kk, vv in iteritems(v))) + else: + rv.append(v) + else: + rv.append(v) + return rv if tuple_factory is list else tuple_factory(rv) + + +def has(cls): + """ + Check whether *cls* is a class with ``attrs`` attributes. + + :param type cls: Class to introspect. + :raise TypeError: If *cls* is not a class. + + :rtype: :class:`bool` + """ + return getattr(cls, "__attrs_attrs__", None) is not None + + +def assoc(inst, **changes): + """ + Copy *inst* and apply *changes*. + + :param inst: Instance of a class with ``attrs`` attributes. + :param changes: Keyword changes in the new copy. + + :return: A copy of inst with *changes* incorporated. + + :raise attr.exceptions.AttrsAttributeNotFoundError: If *attr_name* couldn't + be found on *cls*. + :raise attr.exceptions.NotAnAttrsClassError: If *cls* is not an ``attrs`` + class. + + .. deprecated:: 17.1.0 + Use :func:`evolve` instead. + """ + import warnings + warnings.warn("assoc is deprecated and will be removed after 2018/01.", + DeprecationWarning) + new = copy.copy(inst) + attrs = fields(inst.__class__) + for k, v in iteritems(changes): + a = getattr(attrs, k, NOTHING) + if a is NOTHING: + raise AttrsAttributeNotFoundError( + "{k} is not an attrs attribute on {cl}." + .format(k=k, cl=new.__class__) + ) + _obj_setattr(new, k, v) + return new + + +def evolve(inst, **changes): + """ + Create a new instance, based on *inst* with *changes* applied. + + :param inst: Instance of a class with ``attrs`` attributes. + :param changes: Keyword changes in the new copy. + + :return: A copy of inst with *changes* incorporated. + + :raise TypeError: If *attr_name* couldn't be found in the class + ``__init__``. + :raise attr.exceptions.NotAnAttrsClassError: If *cls* is not an ``attrs`` + class. + + .. versionadded:: 17.1.0 + """ + cls = inst.__class__ + attrs = fields(cls) + for a in attrs: + if not a.init: + continue + attr_name = a.name # To deal with private attributes. + init_name = attr_name if attr_name[0] != "_" else attr_name[1:] + if init_name not in changes: + changes[init_name] = getattr(inst, attr_name) + return cls(**changes)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mercurial/thirdparty/attr/_make.py Sun Oct 01 04:14:16 2017 -0700 @@ -0,0 +1,1059 @@ +from __future__ import absolute_import, division, print_function + +import hashlib +import linecache + +from operator import itemgetter + +from . import _config +from ._compat import PY2, iteritems, isclass, iterkeys, metadata_proxy +from .exceptions import ( + DefaultAlreadySetError, + FrozenInstanceError, + NotAnAttrsClassError, +) + + +# This is used at least twice, so cache it here. +_obj_setattr = object.__setattr__ +_init_convert_pat = "__attr_convert_{}" +_init_factory_pat = "__attr_factory_{}" +_tuple_property_pat = " {attr_name} = property(itemgetter({index}))" +_empty_metadata_singleton = metadata_proxy({}) + + +class _Nothing(object): + """ + Sentinel class to indicate the lack of a value when ``None`` is ambiguous. + + All instances of `_Nothing` are equal. + """ + def __copy__(self): + return self + + def __deepcopy__(self, _): + return self + + def __eq__(self, other): + return other.__class__ == _Nothing + + def __ne__(self, other): + return not self == other + + def __repr__(self): + return "NOTHING" + + def __hash__(self): + return 0xdeadbeef + + +NOTHING = _Nothing() +""" +Sentinel to indicate the lack of a value when ``None`` is ambiguous. +""" + + +def attr(default=NOTHING, validator=None, + repr=True, cmp=True, hash=None, init=True, + convert=None, metadata={}): + """ + Create a new attribute on a class. + + .. warning:: + + Does *not* do anything unless the class is also decorated with + :func:`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 + used to construct a new value (useful for mutable datatypes 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` + will be raised. + + The default can also be set using decorator notation as shown below. + + :type default: Any value. + + :param validator: :func:`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 + passed value. + + The return value is *not* inspected so the validator has to throw an + exception itself. + + If a ``list`` is passed, its items are treated as validators and must + all pass. + + Validators can be globally disabled and re-enabled using + :func:`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 hash: Include this attribute in the generated ``__hash__`` + method. If ``None`` (default), mirror *cmp*'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`` + :param bool init: Include this attribute in the generated ``__init__`` + 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 convert: :func:`callable` that is called by + ``attrs``-generated ``__init__`` methods to convert 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`. + + .. versionchanged:: 17.1.0 *validator* can be a ``list`` now. + .. versionchanged:: 17.1.0 + *hash* is ``None`` and therefore mirrors *cmp* by default . + """ + 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." + ) + return _CountingAttr( + default=default, + validator=validator, + repr=repr, + cmp=cmp, + hash=hash, + init=init, + convert=convert, + metadata=metadata, + ) + + +def _make_attr_tuple_class(cls_name, attr_names): + """ + Create a tuple subclass to hold `Attribute`s for an `attrs` class. + + The subclass is a bare tuple with properties for names. + + class MyClassAttributes(tuple): + __slots__ = () + x = property(itemgetter(0)) + """ + attr_class_name = "{}Attributes".format(cls_name) + attr_class_template = [ + "class {}(tuple):".format(attr_class_name), + " __slots__ = ()", + ] + if attr_names: + for i, attr_name in enumerate(attr_names): + attr_class_template.append(_tuple_property_pat.format( + index=i, + attr_name=attr_name, + )) + else: + attr_class_template.append(" pass") + globs = {"itemgetter": itemgetter} + eval(compile("\n".join(attr_class_template), "", "exec"), globs) + return globs[attr_class_name] + + +def _transform_attrs(cls, these): + """ + Transforms all `_CountingAttr`s on a class into `Attribute`s and saves the + list in `__attrs_attrs__`. + + If *these* is passed, use that and don't look for them on the class. + """ + super_cls = [] + for c in reversed(cls.__mro__[1:-1]): + sub_attrs = getattr(c, "__attrs_attrs__", None) + if sub_attrs is not None: + super_cls.extend(a for a in sub_attrs if a not in super_cls) + if these is None: + ca_list = [(name, attr) + for name, attr + in cls.__dict__.items() + if isinstance(attr, _CountingAttr)] + else: + ca_list = [(name, ca) + for name, ca + in iteritems(these)] + + non_super_attrs = [ + Attribute.from_counting_attr(name=attr_name, ca=ca) + for attr_name, ca + in sorted(ca_list, key=lambda e: e[1].counter) + ] + attr_names = [a.name for a in super_cls + non_super_attrs] + + AttrsClass = _make_attr_tuple_class(cls.__name__, attr_names) + + cls.__attrs_attrs__ = AttrsClass(super_cls + [ + Attribute.from_counting_attr(name=attr_name, ca=ca) + for attr_name, ca + in sorted(ca_list, key=lambda e: e[1].counter) + ]) + + had_default = False + for a in cls.__attrs_attrs__: + if these is None and a not in super_cls: + setattr(cls, a.name, a) + if had_default is True and a.default is NOTHING and a.init is True: + raise ValueError( + "No mandatory attributes allowed after an attribute with a " + "default value or factory. Attribute in question: {a!r}" + .format(a=a) + ) + elif had_default is False and \ + a.default is not NOTHING and \ + a.init is not False: + had_default = True + + +def _frozen_setattrs(self, name, value): + """ + Attached to frozen classes as __setattr__. + """ + raise FrozenInstanceError() + + +def _frozen_delattrs(self, name): + """ + Attached to frozen classes as __delattr__. + """ + raise FrozenInstanceError() + + +def attributes(maybe_cls=None, these=None, repr_ns=None, + repr=True, cmp=True, hash=None, init=True, + slots=False, frozen=False, str=False): + r""" + A class decorator that adds `dunder + <https://wiki.python.org/moin/DunderAlias>`_\ -methods according to the + specified attributes using :func:`attr.ib` or the *these* argument. + + :param these: A dictionary of name to :func:`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. + + If *these* is not ``None``, ``attrs`` will *not* search the class body + for attributes. + + :type these: :class:`dict` of :class:`str` to :func:`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 + namespace explicitly for a more meaningful ``repr`` output. + :param bool repr: Create a ``__repr__`` method with a human readable + represantation 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 type of both classes is *identical*! + :param hash: If ``None`` (default), the ``__hash__`` method is generated + according how *cmp* 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 + None, marking it unhashable (which it is). + 3. If *cmp* is False, ``__hash__`` will be left untouched meaning the + ``__hash__`` method of the superclass will be used (if superclass is + ``object``, this means it will fall back to id-based hashing.). + + Although not recommended, you can decide for yourself and force + ``attrs`` to create one (e.g. if the class is immutable even though you + 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 \ + <https://docs.python.org/3/reference/datamodel.html#object.__hash__>`_ + and the `GitHub issue that led to the default behavior \ + <https://github.com/python-attrs/attrs/issues/136>`_ for more details. + :type hash: ``bool`` or ``None`` + :param bool init: Create a ``__init__`` method that initialiazes 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 frozen: Make instances immutable after initialization. If + someone attempts to modify a frozen instance, + :exc:`attr.exceptions.FrozenInstanceError` is raised. + + Please note: + + 1. This is achieved by installing a custom ``__setattr__`` method + on your class so you can't implement an own one. + + 2. True immutability is impossible in Python. + + 3. This *does* have a minor a runtime performance :ref:`impact + <how-frozen>` when initializing new instances. In other words: + ``__init__`` is slightly slower with ``frozen=True``. + + 4. If a class is frozen, you cannot modify ``self`` in + ``__attrs_post_init__`` or a self-written ``__init__``. You can + circumvent that limitation by using + ``object.__setattr__(self, "attribute_name", value)``. + + .. _slots: https://docs.python.org/3.5/reference/datamodel.html#slots + + .. versionadded:: 16.0.0 *slots* + .. versionadded:: 16.1.0 *frozen* + .. versionadded:: 16.3.0 *str*, and support for ``__attrs_post_init__``. + .. versionchanged:: + 17.1.0 *hash* supports ``None`` as value which is also the default + now. + """ + def wrap(cls): + if getattr(cls, "__class__", None) is None: + raise TypeError("attrs only works with new-style classes.") + + if repr is False and str is True: + raise ValueError( + "__str__ can only be generated if a __repr__ exists." + ) + + if slots: + # Only need this later if we're using slots. + if these is None: + ca_list = [name + for name, attr + in cls.__dict__.items() + if isinstance(attr, _CountingAttr)] + else: + ca_list = list(iterkeys(these)) + _transform_attrs(cls, these) + + # Can't just re-use frozen name because Python's scoping. :( + # Can't compare function objects because Python 2 is terrible. :( + effectively_frozen = _has_frozen_superclass(cls) or frozen + if repr is True: + cls = _add_repr(cls, ns=repr_ns) + if str is True: + cls.__str__ = cls.__repr__ + if cmp is True: + cls = _add_cmp(cls) + + if hash is not True and hash is not False and hash is not None: + raise TypeError( + "Invalid value for hash. Must be True, False, or None." + ) + elif hash is False or (hash is None and cmp is False): + pass + elif hash is True or (hash is None and cmp is True and frozen is True): + cls = _add_hash(cls) + else: + cls.__hash__ = None + + if init is True: + cls = _add_init(cls, effectively_frozen) + if effectively_frozen is True: + cls.__setattr__ = _frozen_setattrs + cls.__delattr__ = _frozen_delattrs + if slots is True: + # slots and frozen require __getstate__/__setstate__ to work + cls = _add_pickle(cls) + if slots is True: + cls_dict = dict(cls.__dict__) + cls_dict["__slots__"] = tuple(ca_list) + for ca_name in ca_list: + # It might not actually be in there, e.g. if using 'these'. + cls_dict.pop(ca_name, None) + cls_dict.pop("__dict__", None) + + qualname = getattr(cls, "__qualname__", None) + cls = type(cls)(cls.__name__, cls.__bases__, cls_dict) + if qualname is not None: + cls.__qualname__ = qualname + + return cls + + # attrs_or class type depends on the usage of the decorator. It's a class + # if it's used as `@attributes` but ``None`` if used # as `@attributes()`. + if maybe_cls is None: + return wrap + else: + return wrap(maybe_cls) + + +if PY2: + def _has_frozen_superclass(cls): + """ + Check whether *cls* has a frozen ancestor by looking at its + __setattr__. + """ + return ( + getattr( + cls.__setattr__, "__module__", None + ) == _frozen_setattrs.__module__ and + cls.__setattr__.__name__ == _frozen_setattrs.__name__ + ) +else: + def _has_frozen_superclass(cls): + """ + Check whether *cls* has a frozen ancestor by looking at its + __setattr__. + """ + return cls.__setattr__ == _frozen_setattrs + + +def _attrs_to_tuple(obj, attrs): + """ + Create a tuple of all values of *obj*'s *attrs*. + """ + return tuple(getattr(obj, a.name) for a in attrs) + + +def _add_hash(cls, attrs=None): + """ + Add a hash method to *cls*. + """ + if attrs is None: + attrs = [a + for a in cls.__attrs_attrs__ + if a.hash is True or (a.hash is None and a.cmp is True)] + + def hash_(self): + """ + Automatically created by attrs. + """ + return hash(_attrs_to_tuple(self, attrs)) + + cls.__hash__ = hash_ + return cls + + +def _add_cmp(cls, attrs=None): + """ + Add comparison methods to *cls*. + """ + if attrs is None: + attrs = [a for a in cls.__attrs_attrs__ if a.cmp] + + def attrs_to_tuple(obj): + """ + Save us some typing. + """ + return _attrs_to_tuple(obj, attrs) + + def eq(self, other): + """ + Automatically created by attrs. + """ + if other.__class__ is self.__class__: + return attrs_to_tuple(self) == attrs_to_tuple(other) + else: + return NotImplemented + + def ne(self, other): + """ + Automatically created by attrs. + """ + result = eq(self, other) + if result is NotImplemented: + return NotImplemented + else: + return not result + + def lt(self, other): + """ + Automatically created by attrs. + """ + if isinstance(other, self.__class__): + return attrs_to_tuple(self) < attrs_to_tuple(other) + else: + return NotImplemented + + def le(self, other): + """ + Automatically created by attrs. + """ + if isinstance(other, self.__class__): + return attrs_to_tuple(self) <= attrs_to_tuple(other) + else: + return NotImplemented + + def gt(self, other): + """ + Automatically created by attrs. + """ + if isinstance(other, self.__class__): + return attrs_to_tuple(self) > attrs_to_tuple(other) + else: + return NotImplemented + + def ge(self, other): + """ + Automatically created by attrs. + """ + if isinstance(other, self.__class__): + return attrs_to_tuple(self) >= attrs_to_tuple(other) + else: + return NotImplemented + + cls.__eq__ = eq + cls.__ne__ = ne + cls.__lt__ = lt + cls.__le__ = le + cls.__gt__ = gt + cls.__ge__ = ge + + return cls + + +def _add_repr(cls, ns=None, attrs=None): + """ + Add a repr method to *cls*. + """ + if attrs is None: + attrs = [a for a in cls.__attrs_attrs__ if a.repr] + + def repr_(self): + """ + Automatically created by attrs. + """ + real_cls = self.__class__ + if ns is None: + qualname = getattr(real_cls, "__qualname__", None) + if qualname is not None: + class_name = qualname.rsplit(">.", 1)[-1] + else: + class_name = real_cls.__name__ + else: + class_name = ns + "." + real_cls.__name__ + + return "{0}({1})".format( + class_name, + ", ".join(a.name + "=" + repr(getattr(self, a.name)) + for a in attrs) + ) + cls.__repr__ = repr_ + return cls + + +def _add_init(cls, frozen): + """ + Add a __init__ method to *cls*. If *frozen* is True, make it immutable. + """ + attrs = [a for a in cls.__attrs_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 = "<attrs generated init {0}>".format( + sha1.hexdigest() + ) + + script, globs = _attrs_to_script( + attrs, + frozen, + getattr(cls, "__attrs_post_init__", False), + ) + locs = {} + bytecode = compile(script, unique_filename, "exec") + attr_dict = dict((a.name, a) for a in attrs) + globs.update({ + "NOTHING": NOTHING, + "attr_dict": attr_dict, + }) + if frozen is True: + # Save the lookup overhead in __init__ if we need to circumvent + # immutability. + globs["_cached_setattr"] = _obj_setattr + eval(bytecode, globs, locs) + init = locs["__init__"] + + # In order of debuggers like PDB being able to step through the code, + # we add a fake linecache entry. + linecache.cache[unique_filename] = ( + len(script), + None, + script.splitlines(True), + unique_filename + ) + cls.__init__ = init + return cls + + +def _add_pickle(cls): + """ + Add pickle helpers, needed for frozen and slotted classes + """ + def _slots_getstate__(obj): + """ + Play nice with pickle. + """ + return tuple(getattr(obj, a.name) for a in fields(obj.__class__)) + + def _slots_setstate__(obj, state): + """ + Play nice with pickle. + """ + __bound_setattr = _obj_setattr.__get__(obj, Attribute) + for a, value in zip(fields(obj.__class__), state): + __bound_setattr(a.name, value) + + cls.__getstate__ = _slots_getstate__ + cls.__setstate__ = _slots_setstate__ + return cls + + +def fields(cls): + """ + Returns the tuple of ``attrs`` attributes for a class. + + The tuple also allows accessing the fields by their names (see below for + examples). + + :param type cls: Class to introspect. + + :raise TypeError: If *cls* is not a class. + :raise attr.exceptions.NotAnAttrsClassError: If *cls* is not an ``attrs`` + class. + + :rtype: tuple (with name accesors) of :class:`attr.Attribute` + + .. versionchanged:: 16.2.0 Returned tuple allows accessing the fields + by name. + """ + if not isclass(cls): + raise TypeError("Passed object must be a class.") + attrs = getattr(cls, "__attrs_attrs__", None) + if attrs is None: + raise NotAnAttrsClassError( + "{cls!r} is not an attrs-decorated class.".format(cls=cls) + ) + return attrs + + +def validate(inst): + """ + Validate all attributes on *inst* that have a validator. + + Leaves all exceptions through. + + :param inst: Instance of a class with ``attrs`` attributes. + """ + if _config._run_validators is False: + return + + for a in fields(inst.__class__): + v = a.validator + if v is not None: + v(inst, a, getattr(inst, a.name)) + + +def _attrs_to_script(attrs, frozen, post_init): + """ + Return a script of an initializer for *attrs* and a dict of globals. + + The globals are expected by the generated script. + + If *frozen* is True, we cannot set the attributes directly so we use + a cached ``object.__setattr__``. + """ + lines = [] + if frozen is True: + lines.append( + # Circumvent the __setattr__ descriptor to save one lookup per + # assignment. + "_setattr = _cached_setattr.__get__(self, self.__class__)" + ) + + def fmt_setter(attr_name, value_var): + return "_setattr('%(attr_name)s', %(value_var)s)" % { + "attr_name": attr_name, + "value_var": value_var, + } + + def fmt_setter_with_converter(attr_name, value_var): + conv_name = _init_convert_pat.format(attr_name) + return "_setattr('%(attr_name)s', %(conv)s(%(value_var)s))" % { + "attr_name": attr_name, + "value_var": value_var, + "conv": conv_name, + } + else: + def fmt_setter(attr_name, value): + return "self.%(attr_name)s = %(value)s" % { + "attr_name": attr_name, + "value": value, + } + + def fmt_setter_with_converter(attr_name, value_var): + conv_name = _init_convert_pat.format(attr_name) + return "self.%(attr_name)s = %(conv)s(%(value_var)s)" % { + "attr_name": attr_name, + "value_var": value_var, + "conv": conv_name, + } + + args = [] + attrs_to_validate = [] + + # This is a dictionary of names to validator and converter callables. + # Injecting this into __init__ globals lets us avoid lookups. + names_for_globals = {} + + for a in attrs: + if a.validator: + attrs_to_validate.append(a) + attr_name = a.name + arg_name = a.name.lstrip("_") + has_factory = isinstance(a.default, Factory) + if has_factory and a.default.takes_self: + maybe_self = "self" + else: + maybe_self = "" + if a.init is False: + if has_factory: + init_factory_name = _init_factory_pat.format(a.name) + if a.convert is not None: + lines.append(fmt_setter_with_converter( + attr_name, + init_factory_name + "({0})".format(maybe_self))) + conv_name = _init_convert_pat.format(a.name) + names_for_globals[conv_name] = a.convert + else: + lines.append(fmt_setter( + attr_name, + init_factory_name + "({0})".format(maybe_self) + )) + names_for_globals[init_factory_name] = a.default.factory + else: + if a.convert is not None: + lines.append(fmt_setter_with_converter( + attr_name, + "attr_dict['{attr_name}'].default" + .format(attr_name=attr_name) + )) + conv_name = _init_convert_pat.format(a.name) + names_for_globals[conv_name] = a.convert + else: + lines.append(fmt_setter( + attr_name, + "attr_dict['{attr_name}'].default" + .format(attr_name=attr_name) + )) + elif a.default is not NOTHING and not has_factory: + args.append( + "{arg_name}=attr_dict['{attr_name}'].default".format( + arg_name=arg_name, + attr_name=attr_name, + ) + ) + if a.convert is not None: + lines.append(fmt_setter_with_converter(attr_name, arg_name)) + names_for_globals[_init_convert_pat.format(a.name)] = a.convert + else: + lines.append(fmt_setter(attr_name, arg_name)) + elif has_factory: + args.append("{arg_name}=NOTHING".format(arg_name=arg_name)) + lines.append("if {arg_name} is not NOTHING:" + .format(arg_name=arg_name)) + init_factory_name = _init_factory_pat.format(a.name) + if a.convert is not None: + lines.append(" " + fmt_setter_with_converter(attr_name, + arg_name)) + lines.append("else:") + lines.append(" " + fmt_setter_with_converter( + attr_name, + init_factory_name + "({0})".format(maybe_self) + )) + names_for_globals[_init_convert_pat.format(a.name)] = a.convert + else: + lines.append(" " + fmt_setter(attr_name, arg_name)) + lines.append("else:") + lines.append(" " + fmt_setter( + attr_name, + init_factory_name + "({0})".format(maybe_self) + )) + names_for_globals[init_factory_name] = a.default.factory + else: + args.append(arg_name) + if a.convert is not None: + lines.append(fmt_setter_with_converter(attr_name, arg_name)) + names_for_globals[_init_convert_pat.format(a.name)] = a.convert + else: + lines.append(fmt_setter(attr_name, arg_name)) + + if attrs_to_validate: # we can skip this if there are no validators. + names_for_globals["_config"] = _config + lines.append("if _config._run_validators is True:") + for a in attrs_to_validate: + val_name = "__attr_validator_{}".format(a.name) + attr_name = "__attr_{}".format(a.name) + lines.append(" {}(self, {}, self.{})".format( + val_name, attr_name, a.name)) + names_for_globals[val_name] = a.validator + names_for_globals[attr_name] = a + if post_init: + lines.append("self.__attrs_post_init__()") + + return """\ +def __init__(self, {args}): + {lines} +""".format( + args=", ".join(args), + lines="\n ".join(lines) if lines else "pass", + ), names_for_globals + + +class Attribute(object): + """ + *Read-only* representation of an attribute. + + :attribute name: The name of the attribute. + + Plus *all* arguments of :func:`attr.ib`. + """ + __slots__ = ( + "name", "default", "validator", "repr", "cmp", "hash", "init", + "convert", "metadata", + ) + + def __init__(self, name, default, validator, repr, cmp, hash, init, + convert=None, metadata=None): + # Cache this descriptor here to speed things up later. + bound_setattr = _obj_setattr.__get__(self, Attribute) + + bound_setattr("name", name) + bound_setattr("default", default) + bound_setattr("validator", validator) + bound_setattr("repr", repr) + bound_setattr("cmp", cmp) + bound_setattr("hash", hash) + bound_setattr("init", init) + bound_setattr("convert", convert) + bound_setattr("metadata", (metadata_proxy(metadata) if metadata + else _empty_metadata_singleton)) + + def __setattr__(self, name, value): + raise FrozenInstanceError() + + @classmethod + def from_counting_attr(cls, name, ca): + inst_dict = { + k: getattr(ca, k) + for k + in Attribute.__slots__ + if k not in ( + "name", "validator", "default", + ) # exclude methods + } + return cls(name=name, validator=ca._validator, default=ca._default, + **inst_dict) + + # Don't use _add_pickle since fields(Attribute) doesn't work + def __getstate__(self): + """ + Play nice with pickle. + """ + return tuple(getattr(self, name) if name != "metadata" + else dict(self.metadata) + for name in self.__slots__) + + def __setstate__(self, state): + """ + Play nice with pickle. + """ + bound_setattr = _obj_setattr.__get__(self, Attribute) + for name, value in zip(self.__slots__, state): + if name != "metadata": + bound_setattr(name, value) + else: + bound_setattr(name, metadata_proxy(value) if value else + _empty_metadata_singleton) + + +_a = [Attribute(name=name, default=NOTHING, validator=None, + repr=True, cmp=True, hash=(name != "metadata"), init=True) + for name in Attribute.__slots__] + +Attribute = _add_hash( + _add_cmp(_add_repr(Attribute, attrs=_a), attrs=_a), + attrs=[a for a in _a if a.hash] +) + + +class _CountingAttr(object): + """ + Intermediate representation of attributes that uses a counter to preserve + the order in which the attributes have been defined. + + *Internal* data structure of the attrs library. Running into is most + likely the result of a bug like a forgotten `@attr.s` decorator. + """ + __slots__ = ("counter", "_default", "repr", "cmp", "hash", "init", + "metadata", "_validator", "convert") + __attrs_attrs__ = tuple( + Attribute(name=name, default=NOTHING, validator=None, + repr=True, cmp=True, hash=True, init=True) + for name + in ("counter", "_default", "repr", "cmp", "hash", "init",) + ) + ( + Attribute(name="metadata", default=None, validator=None, + repr=True, cmp=True, hash=False, init=True), + ) + cls_counter = 0 + + def __init__(self, default, validator, repr, cmp, hash, init, convert, + metadata): + _CountingAttr.cls_counter += 1 + self.counter = _CountingAttr.cls_counter + self._default = default + # If validator is a list/tuple, wrap it using helper validator. + if validator and isinstance(validator, (list, tuple)): + self._validator = and_(*validator) + else: + self._validator = validator + self.repr = repr + self.cmp = cmp + self.hash = hash + self.init = init + self.convert = convert + self.metadata = metadata + + def validator(self, meth): + """ + Decorator that adds *meth* to the list of validators. + + Returns *meth* unchanged. + + .. versionadded:: 17.1.0 + """ + if self._validator is None: + self._validator = meth + else: + self._validator = and_(self._validator, meth) + return meth + + def default(self, meth): + """ + Decorator that allows to set the default for an attribute. + + Returns *meth* unchanged. + + :raises DefaultAlreadySetError: If default has been set before. + + .. versionadded:: 17.1.0 + """ + if self._default is not NOTHING: + raise DefaultAlreadySetError() + + self._default = Factory(meth, takes_self=True) + + return meth + + +_CountingAttr = _add_cmp(_add_repr(_CountingAttr)) + + +@attributes(slots=True, init=False) +class Factory(object): + """ + Stores a factory callable. + + If passed as the default value to :func:`attr.ib`, the factory is used to + generate a new value. + + :param callable factory: A callable that takes either none or exactly one + mandatory positional argument depending on *takes_self*. + :param bool takes_self: Pass the partially initialized instance that is + being initialized as a positional argument. + + .. versionadded:: 17.1.0 *takes_self* + """ + factory = attr() + takes_self = attr() + + def __init__(self, factory, takes_self=False): + """ + `Factory` is part of the default machinery so if we want a default + value here, we have to implement it ourselves. + """ + self.factory = factory + self.takes_self = takes_self + + +def make_class(name, attrs, bases=(object,), **attributes_arguments): + """ + A quick way to create a new class called *name* with *attrs*. + + :param name: The name for the new class. + :type name: str + + :param attrs: A list of names or a dictionary of mappings of names to + attributes. + :type attrs: :class:`list` or :class:`dict` + + :param tuple bases: Classes that the new class will subclass. + + :param attributes_arguments: Passed unmodified to :func:`attr.s`. + + :return: A new class with *attrs*. + :rtype: type + + .. versionadded:: 17.1.0 *bases* + """ + if isinstance(attrs, dict): + cls_dict = attrs + elif isinstance(attrs, (list, tuple)): + cls_dict = dict((a, attr()) for a in attrs) + else: + raise TypeError("attrs argument must be a dict or a list.") + + return attributes(**attributes_arguments)(type(name, bases, cls_dict)) + + +# These are required by whithin this module so we define them here and merely +# import into .validators. + + +@attributes(slots=True, hash=True) +class _AndValidator(object): + """ + Compose many validators to a single one. + """ + _validators = attr() + + def __call__(self, inst, attr, value): + for v in self._validators: + v(inst, attr, value) + + +def and_(*validators): + """ + A validator that composes multiple validators into one. + + When called on a value, it runs all wrapped validators. + + :param validators: Arbitrary number of validators. + :type validators: callables + + .. versionadded:: 17.1.0 + """ + vals = [] + for validator in validators: + vals.extend( + validator._validators if isinstance(validator, _AndValidator) + else [validator] + ) + + return _AndValidator(tuple(vals))
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mercurial/thirdparty/attr/converters.py Sun Oct 01 04:14:16 2017 -0700 @@ -0,0 +1,24 @@ +""" +Commonly useful converters. +""" + +from __future__ import absolute_import, division, print_function + + +def optional(converter): + """ + A converter that allows an attribute to be optional. An optional attribute + is one which can be set to ``None``. + + :param callable converter: the converter that is used for non-``None`` + values. + + .. versionadded:: 17.1.0 + """ + + def optional_converter(val): + if val is None: + return None + return converter(val) + + return optional_converter
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mercurial/thirdparty/attr/exceptions.py Sun Oct 01 04:14:16 2017 -0700 @@ -0,0 +1,39 @@ +from __future__ import absolute_import, division, print_function + + +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`. + + .. versionadded:: 16.1.0 + """ + msg = "can't set attribute" + args = [msg] + + +class AttrsAttributeNotFoundError(ValueError): + """ + An ``attrs`` function couldn't find an attribute that the user asked for. + + .. versionadded:: 16.2.0 + """ + + +class NotAnAttrsClassError(ValueError): + """ + A non-``attrs`` class has been passed into an ``attrs`` function. + + .. versionadded:: 16.2.0 + """ + + +class DefaultAlreadySetError(RuntimeError): + """ + A default has been set using ``attr.ib()`` and is attempted to be reset + using the decorator. + + .. versionadded:: 17.1.0 + """
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mercurial/thirdparty/attr/filters.py Sun Oct 01 04:14:16 2017 -0700 @@ -0,0 +1,52 @@ +""" +Commonly useful filters for :func:`attr.asdict`. +""" + +from __future__ import absolute_import, division, print_function + +from ._compat import isclass +from ._make import Attribute + + +def _split_what(what): + """ + Returns a tuple of `frozenset`s of classes and attributes. + """ + return ( + frozenset(cls for cls in what if isclass(cls)), + frozenset(cls for cls in what if isinstance(cls, Attribute)), + ) + + +def include(*what): + """ + Whitelist *what*. + + :param what: What to whitelist. + :type what: :class:`list` of :class:`type` or :class:`attr.Attribute`\ s + + :rtype: :class:`callable` + """ + cls, attrs = _split_what(what) + + def include_(attribute, value): + return value.__class__ in cls or attribute in attrs + + return include_ + + +def exclude(*what): + """ + Blacklist *what*. + + :param what: What to blacklist. + :type what: :class:`list` of classes or :class:`attr.Attribute`\ s. + + :rtype: :class:`callable` + """ + cls, attrs = _split_what(what) + + def exclude_(attribute, value): + return value.__class__ not in cls and attribute not in attrs + + return exclude_
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mercurial/thirdparty/attr/validators.py Sun Oct 01 04:14:16 2017 -0700 @@ -0,0 +1,166 @@ +""" +Commonly useful validators. +""" + +from __future__ import absolute_import, division, print_function + +from ._make import attr, attributes, and_, _AndValidator + + +__all__ = [ + "and_", + "in_", + "instance_of", + "optional", + "provides", +] + + +@attributes(repr=False, slots=True, hash=True) +class _InstanceOfValidator(object): + type = attr() + + def __call__(self, inst, attr, value): + """ + We use a callable class to be able to change the ``__repr__``. + """ + if not isinstance(value, self.type): + raise TypeError( + "'{name}' must be {type!r} (got {value!r} that is a " + "{actual!r})." + .format(name=attr.name, type=self.type, + actual=value.__class__, value=value), + attr, self.type, value, + ) + + def __repr__(self): + return ( + "<instance_of validator for type {type!r}>" + .format(type=self.type) + ) + + +def instance_of(type): + """ + A validator that raises a :exc:`TypeError` if the initializer is called + with a wrong type for this particular attribute (checks are perfomed using + :func:`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 + got. + """ + return _InstanceOfValidator(type) + + +@attributes(repr=False, slots=True, hash=True) +class _ProvidesValidator(object): + interface = attr() + + def __call__(self, inst, attr, value): + """ + We use a callable class to be able to change the ``__repr__``. + """ + if not self.interface.providedBy(value): + raise TypeError( + "'{name}' must provide {interface!r} which {value!r} " + "doesn't." + .format(name=attr.name, interface=self.interface, value=value), + attr, self.interface, value, + ) + + def __repr__(self): + return ( + "<provides validator for interface {interface!r}>" + .format(interface=self.interface) + ) + + +def provides(interface): + """ + A validator that raises a :exc:`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 + <https://zopeinterface.readthedocs.io/en/latest/>`_). + + :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 + value it got. + """ + return _ProvidesValidator(interface) + + +@attributes(repr=False, slots=True, hash=True) +class _OptionalValidator(object): + validator = attr() + + def __call__(self, inst, attr, value): + if value is None: + return + + self.validator(inst, attr, value) + + def __repr__(self): + return ( + "<optional validator for {what} or None>" + .format(what=repr(self.validator)) + ) + + +def optional(validator): + """ + A validator that makes an attribute optional. An optional attribute is one + which can be set to ``None`` in addition to satisfying the requirements of + the sub-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. + + .. versionadded:: 15.1.0 + .. versionchanged:: 17.1.0 *validator* can be a list of validators. + """ + if isinstance(validator, list): + return _OptionalValidator(_AndValidator(validator)) + return _OptionalValidator(validator) + + +@attributes(repr=False, slots=True, hash=True) +class _InValidator(object): + options = attr() + + def __call__(self, inst, attr, value): + if value not in self.options: + raise ValueError( + "'{name}' must be in {options!r} (got {value!r})" + .format(name=attr.name, options=self.options, value=value) + ) + + def __repr__(self): + return ( + "<in_ validator with options {options!r}>" + .format(options=self.options) + ) + + +def in_(options): + """ + A validator that raises a :exc:`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`, ... + + :raises ValueError: With a human readable error message, the attribute (of + type :class:`attr.Attribute`), the expected options, and the value it + got. + + .. versionadded:: 17.1.0 + """ + return _InValidator(options)
--- a/setup.py Sun Oct 01 04:04:18 2017 -0700 +++ b/setup.py Sun Oct 01 04:14:16 2017 -0700 @@ -709,6 +709,8 @@ 'mercurial.hgweb', 'mercurial.httpclient', 'mercurial.pure', + 'mercurial.thirdparty', + 'mercurial.thirdparty.attr', 'hgext', 'hgext.convert', 'hgext.fsmonitor', 'hgext.fsmonitor.pywatchman', 'hgext.highlight', 'hgext.largefiles', 'hgext.zeroconf', 'hgext3rd',