Mercurial > public > mercurial-scm > hg
diff mercurial/thirdparty/attr/_make.py @ 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 | |
children | a5493a251ad3 |
line wrap: on
line diff
--- /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))