aboutsummaryrefslogtreecommitdiffstats
path: root/python/attr
diff options
context:
space:
mode:
Diffstat (limited to 'python/attr')
-rw-r--r--python/attr/__init__.py65
-rw-r--r--python/attr/__init__.pyi252
-rw-r--r--python/attr/_compat.py163
-rw-r--r--python/attr/_config.py23
-rw-r--r--python/attr/_funcs.py290
-rw-r--r--python/attr/_make.py2034
-rw-r--r--python/attr/converters.py78
-rw-r--r--python/attr/converters.pyi12
-rw-r--r--python/attr/exceptions.py57
-rw-r--r--python/attr/exceptions.pyi7
-rw-r--r--python/attr/filters.py52
-rw-r--r--python/attr/filters.pyi5
-rw-r--r--python/attr/py.typed0
-rw-r--r--python/attr/validators.py170
-rw-r--r--python/attr/validators.pyi14
15 files changed, 3222 insertions, 0 deletions
diff --git a/python/attr/__init__.py b/python/attr/__init__.py
new file mode 100644
index 0000000..debfd57
--- /dev/null
+++ b/python/attr/__init__.py
@@ -0,0 +1,65 @@
+from __future__ import absolute_import, division, print_function
+
+from functools import partial
+
+from . import converters, exceptions, filters, validators
+from ._config import get_run_validators, set_run_validators
+from ._funcs import asdict, assoc, astuple, evolve, has
+from ._make import (
+ NOTHING,
+ Attribute,
+ Factory,
+ attrib,
+ attrs,
+ fields,
+ fields_dict,
+ make_class,
+ validate,
+)
+
+
+__version__ = "18.2.0"
+
+__title__ = "attrs"
+__description__ = "Classes Without Boilerplate"
+__url__ = "https://www.attrs.org/"
+__uri__ = __url__
+__doc__ = __description__ + " <" + __uri__ + ">"
+
+__author__ = "Hynek Schlawack"
+__email__ = "hs@ox.cx"
+
+__license__ = "MIT"
+__copyright__ = "Copyright (c) 2015 Hynek Schlawack"
+
+
+s = attributes = attrs
+ib = attr = attrib
+dataclass = partial(attrs, auto_attribs=True) # happy Easter ;)
+
+__all__ = [
+ "Attribute",
+ "Factory",
+ "NOTHING",
+ "asdict",
+ "assoc",
+ "astuple",
+ "attr",
+ "attrib",
+ "attributes",
+ "attrs",
+ "converters",
+ "evolve",
+ "exceptions",
+ "fields",
+ "fields_dict",
+ "filters",
+ "get_run_validators",
+ "has",
+ "ib",
+ "make_class",
+ "s",
+ "set_run_validators",
+ "validate",
+ "validators",
+]
diff --git a/python/attr/__init__.pyi b/python/attr/__init__.pyi
new file mode 100644
index 0000000..492fb85
--- /dev/null
+++ b/python/attr/__init__.pyi
@@ -0,0 +1,252 @@
+from typing import (
+ Any,
+ Callable,
+ Dict,
+ Generic,
+ List,
+ Optional,
+ Sequence,
+ Mapping,
+ Tuple,
+ Type,
+ TypeVar,
+ Union,
+ overload,
+)
+
+# `import X as X` is required to make these public
+from . import exceptions as exceptions
+from . import filters as filters
+from . import converters as converters
+from . import validators as validators
+
+_T = TypeVar("_T")
+_C = TypeVar("_C", bound=type)
+
+_ValidatorType = Callable[[Any, Attribute, _T], Any]
+_ConverterType = Callable[[Any], _T]
+_FilterType = Callable[[Attribute, Any], bool]
+# 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.
+_ValidatorArgType = Union[_ValidatorType[_T], Sequence[_ValidatorType[_T]]]
+
+# _make --
+
+NOTHING: object
+
+# NOTE: Factory lies about its return type to make this possible: `x: List[int] = Factory(list)`
+# Work around mypy issue #4554 in the common case by using an overload.
+@overload
+def Factory(factory: Callable[[], _T]) -> _T: ...
+@overload
+def Factory(
+ factory: Union[Callable[[Any], _T], Callable[[], _T]],
+ takes_self: bool = ...,
+) -> _T: ...
+
+class Attribute(Generic[_T]):
+ name: str
+ default: Optional[_T]
+ validator: Optional[_ValidatorType[_T]]
+ repr: bool
+ cmp: 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) -> bool: ...
+ def __le__(self, x: Attribute) -> bool: ...
+ def __gt__(self, x: Attribute) -> bool: ...
+ def __ge__(self, x: Attribute) -> bool: ...
+
+# NOTE: We had several choices for the annotation to use for type arg:
+# 1) Type[_T]
+# - Pros: Handles simple cases correctly
+# - Cons: Might produce less informative errors in the case of conflicting TypeVars
+# e.g. `attr.ib(default='bad', type=int)`
+# 2) Callable[..., _T]
+# - Pros: Better error messages than #1 for conflicting TypeVars
+# - Cons: Terrible error messages for validator checks.
+# e.g. attr.ib(type=int, validator=validate_str)
+# -> error: Cannot infer function type argument
+# 3) type (and do all of the work in the mypy plugin)
+# - Pros: Simple here, and we could customize the plugin with our own errors.
+# - Cons: Would need to write mypy plugin code to handle all the cases.
+# We chose option #1.
+
+# `attr` lies about its return type to make the following possible:
+# attr() -> Any
+# attr(8) -> int
+# attr(validator=<some callable>) -> Whatever the callable expects.
+# This makes this type of assignments possible:
+# x: int = attr(8)
+#
+# This form catches explicit None or no default but with no other arguments returns Any.
+@overload
+def attrib(
+ default: None = ...,
+ validator: None = ...,
+ repr: bool = ...,
+ cmp: bool = ...,
+ hash: Optional[bool] = ...,
+ init: bool = ...,
+ convert: None = ...,
+ metadata: Optional[Mapping[Any, Any]] = ...,
+ type: None = ...,
+ converter: None = ...,
+ factory: None = ...,
+ kw_only: bool = ...,
+) -> Any: ...
+
+# This form catches an explicit None or no default and infers the type from the other arguments.
+@overload
+def attrib(
+ default: None = ...,
+ validator: Optional[_ValidatorArgType[_T]] = ...,
+ repr: bool = ...,
+ cmp: 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 = ...,
+) -> _T: ...
+
+# This form catches an explicit default argument.
+@overload
+def attrib(
+ default: _T,
+ validator: Optional[_ValidatorArgType[_T]] = ...,
+ repr: bool = ...,
+ cmp: 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 = ...,
+) -> _T: ...
+
+# This form covers type=non-Type: e.g. forward references (str), Any
+@overload
+def attrib(
+ default: Optional[_T] = ...,
+ validator: Optional[_ValidatorArgType[_T]] = ...,
+ repr: bool = ...,
+ cmp: 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 = ...,
+) -> Any: ...
+@overload
+def attrs(
+ maybe_cls: _C,
+ these: Optional[Dict[str, Any]] = ...,
+ repr_ns: Optional[str] = ...,
+ repr: bool = ...,
+ cmp: bool = ...,
+ hash: Optional[bool] = ...,
+ init: bool = ...,
+ slots: bool = ...,
+ frozen: bool = ...,
+ weakref_slot: bool = ...,
+ str: bool = ...,
+ auto_attribs: bool = ...,
+ kw_only: bool = ...,
+ cache_hash: bool = ...,
+) -> _C: ...
+@overload
+def attrs(
+ maybe_cls: None = ...,
+ these: Optional[Dict[str, Any]] = ...,
+ repr_ns: Optional[str] = ...,
+ repr: bool = ...,
+ cmp: bool = ...,
+ hash: Optional[bool] = ...,
+ init: bool = ...,
+ slots: bool = ...,
+ frozen: bool = ...,
+ weakref_slot: bool = ...,
+ str: bool = ...,
+ auto_attribs: bool = ...,
+ kw_only: bool = ...,
+ cache_hash: bool = ...,
+) -> Callable[[_C], _C]: ...
+
+# TODO: add support for returning NamedTuple from the mypy plugin
+class _Fields(Tuple[Attribute, ...]):
+ def __getattr__(self, name: str) -> Attribute: ...
+
+def fields(cls: type) -> _Fields: ...
+def fields_dict(cls: type) -> Dict[str, Attribute]: ...
+def validate(inst: Any) -> None: ...
+
+# TODO: add support for returning a proper attrs class from the mypy plugin
+# we use Any instead of _CountingAttr so that e.g. `make_class('Foo', [attr.ib()])` is valid
+def make_class(
+ name: str,
+ attrs: Union[List[str], Tuple[str, ...], Dict[str, Any]],
+ bases: Tuple[type, ...] = ...,
+ repr_ns: Optional[str] = ...,
+ repr: bool = ...,
+ cmp: bool = ...,
+ hash: Optional[bool] = ...,
+ init: bool = ...,
+ slots: bool = ...,
+ frozen: bool = ...,
+ weakref_slot: bool = ...,
+ str: bool = ...,
+ auto_attribs: bool = ...,
+ kw_only: bool = ...,
+ cache_hash: bool = ...,
+) -> type: ...
+
+# _funcs --
+
+# TODO: add support for returning TypedDict from the mypy plugin
+# FIXME: asdict/astuple do not honor their factory args. waiting on one of these:
+# https://github.com/python/mypy/issues/4236
+# https://github.com/python/typing/issues/253
+def asdict(
+ inst: Any,
+ recurse: bool = ...,
+ filter: Optional[_FilterType] = ...,
+ dict_factory: Type[Mapping[Any, Any]] = ...,
+ retain_collection_types: bool = ...,
+) -> Dict[str, Any]: ...
+
+# TODO: add support for returning NamedTuple from the mypy plugin
+def astuple(
+ inst: Any,
+ recurse: bool = ...,
+ filter: Optional[_FilterType] = ...,
+ tuple_factory: Type[Sequence] = ...,
+ retain_collection_types: bool = ...,
+) -> Tuple[Any, ...]: ...
+def has(cls: type) -> bool: ...
+def assoc(inst: _T, **changes: Any) -> _T: ...
+def evolve(inst: _T, **changes: Any) -> _T: ...
+
+# _config --
+
+def set_run_validators(run: bool) -> None: ...
+def get_run_validators() -> bool: ...
+
+# aliases --
+
+s = attributes = attrs
+ib = attr = attrib
+dataclass = attrs # Technically, partial(attrs, auto_attribs=True) ;)
diff --git a/python/attr/_compat.py b/python/attr/_compat.py
new file mode 100644
index 0000000..5bb0659
--- /dev/null
+++ b/python/attr/_compat.py
@@ -0,0 +1,163 @@
+from __future__ import absolute_import, division, print_function
+
+import platform
+import sys
+import types
+import warnings
+
+
+PY2 = sys.version_info[0] == 2
+PYPY = platform.python_implementation() == "PyPy"
+
+
+if PYPY or sys.version_info[:2] >= (3, 6):
+ ordered_dict = dict
+else:
+ from collections import OrderedDict
+
+ ordered_dict = OrderedDict
+
+
+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()
+
+ # 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 metadata_proxy(d):
+ return types.MappingProxyType(dict(d))
+
+
+def import_ctypes():
+ """
+ Moved into a function for testability.
+ """
+ import ctypes
+
+ return ctypes
+
+
+if not PY2:
+
+ def just_warn(*args, **kw):
+ """
+ We only warn on Python 3 because we are not aware of any concrete
+ consequences of not setting the cell on Python 2.
+ """
+ warnings.warn(
+ "Missing ctypes. Some features like bare super() or accessing "
+ "__class__ will not work with slots classes.",
+ RuntimeWarning,
+ stacklevel=2,
+ )
+
+
+else:
+
+ def just_warn(*args, **kw): # pragma: nocover
+ """
+ We only warn on Python 3 because we are not aware of any concrete
+ consequences of not setting the cell on Python 2.
+ """
+
+
+def make_set_closure_cell():
+ """
+ Moved into a function for testability.
+ """
+ if PYPY: # pragma: no cover
+
+ def set_closure_cell(cell, value):
+ cell.__setstate__((value,))
+
+ else:
+ try:
+ ctypes = import_ctypes()
+
+ 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
+
+
+set_closure_cell = make_set_closure_cell()
diff --git a/python/attr/_config.py b/python/attr/_config.py
new file mode 100644
index 0000000..8ec9209
--- /dev/null
+++ b/python/attr/_config.py
@@ -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
diff --git a/python/attr/_funcs.py b/python/attr/_funcs.py
new file mode 100644
index 0000000..b61d239
--- /dev/null
+++ b/python/attr/_funcs.py
@@ -0,0 +1,290 @@
+from __future__ import absolute_import, division, print_function
+
+import copy
+
+from ._compat import iteritems
+from ._make import NOTHING, _obj_setattr, fields
+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 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 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, True, filter, dict_factory, retain_collection_types
+ )
+ elif isinstance(v, (tuple, list, set)):
+ cf = v.__class__ if retain_collection_types is True else list
+ rv[a.name] = cf(
+ [
+ _asdict_anything(
+ i, filter, dict_factory, retain_collection_types
+ )
+ for i in v
+ ]
+ )
+ elif isinstance(v, dict):
+ df = dict_factory
+ rv[a.name] = df(
+ (
+ _asdict_anything(
+ kk, filter, df, retain_collection_types
+ ),
+ _asdict_anything(
+ vv, filter, df, retain_collection_types
+ ),
+ )
+ for kk, vv in iteritems(v)
+ )
+ else:
+ rv[a.name] = v
+ else:
+ rv[a.name] = v
+ return rv
+
+
+def _asdict_anything(val, filter, dict_factory, retain_collection_types):
+ """
+ ``asdict`` only works on attrs instances, this works on anything.
+ """
+ if getattr(val.__class__, "__attrs_attrs__", None) is not None:
+ # Attrs class.
+ rv = asdict(val, True, filter, dict_factory, retain_collection_types)
+ elif isinstance(val, (tuple, list, set)):
+ cf = val.__class__ if retain_collection_types is True else list
+ rv = cf(
+ [
+ _asdict_anything(
+ i, filter, dict_factory, retain_collection_types
+ )
+ for i in val
+ ]
+ )
+ elif isinstance(val, dict):
+ df = dict_factory
+ rv = df(
+ (
+ _asdict_anything(kk, filter, df, retain_collection_types),
+ _asdict_anything(vv, filter, df, retain_collection_types),
+ )
+ for kk, vv in iteritems(val)
+ )
+ else:
+ rv = val
+ 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,
+ stacklevel=2,
+ )
+ 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)
diff --git a/python/attr/_make.py b/python/attr/_make.py
new file mode 100644
index 0000000..f7fd05e
--- /dev/null
+++ b/python/attr/_make.py
@@ -0,0 +1,2034 @@
+from __future__ import absolute_import, division, print_function
+
+import copy
+import hashlib
+import linecache
+import sys
+import threading
+import warnings
+
+from operator import itemgetter
+
+from . import _config
+from ._compat import (
+ PY2,
+ isclass,
+ iteritems,
+ metadata_proxy,
+ ordered_dict,
+ set_closure_cell,
+)
+from .exceptions import (
+ DefaultAlreadySetError,
+ FrozenInstanceError,
+ NotAnAttrsClassError,
+ PythonTooOldError,
+ UnannotatedAttributeError,
+)
+
+
+# This is used at least twice, so cache it here.
+_obj_setattr = object.__setattr__
+_init_converter_pat = "__attr_converter_{}"
+_init_factory_pat = "__attr_factory_{}"
+_tuple_property_pat = (
+ " {attr_name} = _attrs_property(_attrs_itemgetter({index}))"
+)
+_classvar_prefixes = ("typing.ClassVar", "t.ClassVar", "ClassVar")
+# we don't use a double-underscore prefix because that triggers
+# name mangling when trying to create a slot for the field
+# (when slots=True)
+_hash_cache_field = "_attrs_cached_hash"
+
+_empty_metadata_singleton = metadata_proxy({})
+
+
+class _Nothing(object):
+ """
+ Sentinel class to indicate the lack of a value when ``None`` is ambiguous.
+
+ ``_Nothing`` is a singleton. There is only ever one of it.
+ """
+
+ _singleton = None
+
+ def __new__(cls):
+ if _Nothing._singleton is None:
+ _Nothing._singleton = super(_Nothing, cls).__new__(cls)
+ return _Nothing._singleton
+
+ def __repr__(self):
+ return "NOTHING"
+
+
+NOTHING = _Nothing()
+"""
+Sentinel to indicate the lack of a value when ``None`` is ambiguous.
+"""
+
+
+def attrib(
+ default=NOTHING,
+ validator=None,
+ repr=True,
+ cmp=True,
+ hash=None,
+ init=True,
+ convert=None,
+ metadata=None,
+ type=None,
+ converter=None,
+ factory=None,
+ kw_only=False,
+):
+ """
+ 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 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`
+ will be raised.
+
+ The default can also be set using decorator notation as shown below.
+
+ :type default: Any value.
+
+ :param callable factory: Syntactic sugar for
+ ``default=attr.Factory(callable)``.
+
+ :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 converter: :func:`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`.
+ :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 <https://www.python.org/dev/peps/pep-0526/>`_).
+ This argument is provided for backward compatibility.
+ Regardless of the approach used, the type will be stored on
+ ``Attribute.type``.
+
+ 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 <types>`.
+ :param kw_only: Make this attribute keyword-only (Python 3+)
+ in the generated ``__init__`` (if ``init`` is ``False``, this
+ parameter is ignored).
+
+ .. versionadded:: 15.2.0 *convert*
+ .. 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.
+ .. versionadded:: 17.3.0 *type*
+ .. deprecated:: 17.4.0 *convert*
+ .. versionadded:: 17.4.0 *converter* as a replacement for the deprecated
+ *convert* to achieve consistency with other noun-based arguments.
+ .. versionadded:: 18.1.0
+ ``factory=f`` is syntactic sugar for ``default=attr.Factory(f)``.
+ .. versionadded:: 18.2.0 *kw_only*
+ """
+ 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(
+ "The `default` and `factory` arguments are mutually "
+ "exclusive."
+ )
+ if not callable(factory):
+ raise ValueError("The `factory` argument must be a callable.")
+ default = Factory(factory)
+
+ if metadata is None:
+ metadata = {}
+
+ return _CountingAttr(
+ default=default,
+ validator=validator,
+ repr=repr,
+ cmp=cmp,
+ hash=hash,
+ init=init,
+ converter=converter,
+ metadata=metadata,
+ type=type,
+ kw_only=kw_only,
+ )
+
+
+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 = {"_attrs_itemgetter": itemgetter, "_attrs_property": property}
+ eval(compile("\n".join(attr_class_template), "", "exec"), globs)
+
+ return globs[attr_class_name]
+
+
+# Tuple class for extracted attributes from a class definition.
+# `base_attrs` is a subset of `attrs`.
+_Attributes = _make_attr_tuple_class(
+ "_Attributes",
+ [
+ # all attributes to build dunder methods for
+ "attrs",
+ # attributes that have been inherited
+ "base_attrs",
+ # map inherited attributes to their originating classes
+ "base_attrs_map",
+ ],
+)
+
+
+def _is_class_var(annot):
+ """
+ Check whether *annot* is a typing.ClassVar.
+
+ The string comparison hack is used to avoid evaluating all string
+ annotations which would put attrs-based classes at a performance
+ disadvantage compared to plain old classes.
+ """
+ return str(annot).startswith(_classvar_prefixes)
+
+
+def _get_annotations(cls):
+ """
+ Get annotations for *cls*.
+ """
+ anns = getattr(cls, "__annotations__", None)
+ if anns is None:
+ return {}
+
+ # Verify that the annotations aren't merely inherited.
+ for base_cls in cls.__mro__[1:]:
+ if anns is getattr(base_cls, "__annotations__", None):
+ return {}
+
+ return anns
+
+
+def _counter_getter(e):
+ """
+ Key function for sorting to avoid re-creating a lambda for every class.
+ """
+ return e[1].counter
+
+
+def _transform_attrs(cls, these, auto_attribs, kw_only):
+ """
+ Transform all `_CountingAttr`s on a class into `Attribute`s.
+
+ If *these* is passed, use that and don't look for them on the class.
+
+ Return an `_Attributes`.
+ """
+ cd = cls.__dict__
+ anns = _get_annotations(cls)
+
+ if these is not None:
+ ca_list = [(name, ca) for name, ca in iteritems(these)]
+
+ if not isinstance(these, ordered_dict):
+ ca_list.sort(key=_counter_getter)
+ elif auto_attribs is True:
+ ca_names = {
+ name
+ for name, attr in cd.items()
+ if isinstance(attr, _CountingAttr)
+ }
+ ca_list = []
+ annot_names = set()
+ for attr_name, type in anns.items():
+ if _is_class_var(type):
+ continue
+ annot_names.add(attr_name)
+ a = cd.get(attr_name, NOTHING)
+ if not isinstance(a, _CountingAttr):
+ if a is NOTHING:
+ a = attrib()
+ else:
+ a = attrib(default=a)
+ ca_list.append((attr_name, a))
+
+ unannotated = ca_names - annot_names
+ if len(unannotated) > 0:
+ raise UnannotatedAttributeError(
+ "The following `attr.ib`s lack a type annotation: "
+ + ", ".join(
+ sorted(unannotated, key=lambda n: cd.get(n).counter)
+ )
+ + "."
+ )
+ else:
+ ca_list = sorted(
+ (
+ (name, attr)
+ for name, attr in cd.items()
+ if isinstance(attr, _CountingAttr)
+ ),
+ key=lambda e: e[1].counter,
+ )
+
+ own_attrs = [
+ Attribute.from_counting_attr(
+ name=attr_name, ca=ca, type=anns.get(attr_name)
+ )
+ for attr_name, ca in ca_list
+ ]
+
+ base_attrs = []
+ base_attr_map = {} # A dictionary of base attrs to their classes.
+ taken_attr_names = {a.name: a for a in own_attrs}
+
+ # Traverse the MRO and collect attributes.
+ for base_cls in cls.__mro__[1:-1]:
+ sub_attrs = getattr(base_cls, "__attrs_attrs__", None)
+ if sub_attrs is not None:
+ for a in sub_attrs:
+ prev_a = taken_attr_names.get(a.name)
+ # Only add an attribute if it hasn't been defined before. This
+ # allows for overwriting attribute definitions by subclassing.
+ if prev_a is None:
+ base_attrs.append(a)
+ taken_attr_names[a.name] = a
+ base_attr_map[a.name] = base_cls
+
+ attr_names = [a.name for a in base_attrs + own_attrs]
+
+ AttrsClass = _make_attr_tuple_class(cls.__name__, attr_names)
+
+ if kw_only:
+ own_attrs = [a._assoc(kw_only=True) for a in own_attrs]
+ base_attrs = [a._assoc(kw_only=True) for a in base_attrs]
+
+ attrs = AttrsClass(base_attrs + own_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
+ ):
+ 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
+ ):
+ had_default = True
+ if was_kw_only is True and a.kw_only is False:
+ raise ValueError(
+ "Non keyword-only attributes are not allowed after a "
+ "keyword-only attribute. 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))
+
+
+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()
+
+
+class _ClassBuilder(object):
+ """
+ Iteratively build *one* class.
+ """
+
+ __slots__ = (
+ "_cls",
+ "_cls_dict",
+ "_attrs",
+ "_base_names",
+ "_attr_names",
+ "_slots",
+ "_frozen",
+ "_weakref_slot",
+ "_cache_hash",
+ "_has_post_init",
+ "_delete_attribs",
+ "_base_attr_map",
+ )
+
+ def __init__(
+ self,
+ cls,
+ these,
+ slots,
+ frozen,
+ weakref_slot,
+ auto_attribs,
+ kw_only,
+ cache_hash,
+ ):
+ attrs, base_attrs, base_map = _transform_attrs(
+ cls, these, auto_attribs, kw_only
+ )
+
+ self._cls = cls
+ self._cls_dict = dict(cls.__dict__) if slots else {}
+ self._attrs = attrs
+ self._base_names = set(a.name for a in base_attrs)
+ self._base_attr_map = base_map
+ self._attr_names = tuple(a.name for a in attrs)
+ self._slots = slots
+ self._frozen = frozen or _has_frozen_base_class(cls)
+ self._weakref_slot = weakref_slot
+ self._cache_hash = cache_hash
+ self._has_post_init = bool(getattr(cls, "__attrs_post_init__", False))
+ self._delete_attribs = not bool(these)
+
+ self._cls_dict["__attrs_attrs__"] = self._attrs
+
+ if frozen:
+ self._cls_dict["__setattr__"] = _frozen_setattrs
+ self._cls_dict["__delattr__"] = _frozen_delattrs
+
+ def __repr__(self):
+ return "<_ClassBuilder(cls={cls})>".format(cls=self._cls.__name__)
+
+ def build_class(self):
+ """
+ Finalize class based on the accumulated configuration.
+
+ Builder cannot be used after calling this method.
+ """
+ if self._slots is True:
+ return self._create_slots_class()
+ else:
+ return self._patch_original_class()
+
+ def _patch_original_class(self):
+ """
+ Apply accumulated methods and return the class.
+ """
+ cls = self._cls
+ base_names = self._base_names
+
+ # Clean class of attribute definitions (`attr.ib()`s).
+ if self._delete_attribs:
+ for name in self._attr_names:
+ if (
+ name not in base_names
+ and getattr(cls, name, None) is not None
+ ):
+ try:
+ delattr(cls, name)
+ except AttributeError:
+ # This can happen if a base class defines a class
+ # variable and we want to set an attribute with the
+ # same name by using only a type annotation.
+ pass
+
+ # Attach our dunder methods.
+ for name, value in self._cls_dict.items():
+ setattr(cls, name, value)
+
+ return cls
+
+ def _create_slots_class(self):
+ """
+ Build and return a new class with a `__slots__` attribute.
+ """
+ base_names = self._base_names
+ cd = {
+ k: v
+ for k, v in iteritems(self._cls_dict)
+ if k not in tuple(self._attr_names) + ("__dict__", "__weakref__")
+ }
+
+ weakref_inherited = False
+
+ # Traverse the MRO to check for an existing __weakref__.
+ for base_cls in self._cls.__mro__[1:-1]:
+ if "__weakref__" in getattr(base_cls, "__dict__", ()):
+ weakref_inherited = True
+ break
+
+ names = self._attr_names
+ if (
+ self._weakref_slot
+ and "__weakref__" not in getattr(self._cls, "__slots__", ())
+ and "__weakref__" not in names
+ and not weakref_inherited
+ ):
+ names += ("__weakref__",)
+
+ # We only add the names of attributes that aren't inherited.
+ # Settings __slots__ to inherited attributes wastes memory.
+ slot_names = [name for name in names if name not in base_names]
+ if self._cache_hash:
+ slot_names.append(_hash_cache_field)
+ cd["__slots__"] = tuple(slot_names)
+
+ qualname = getattr(self._cls, "__qualname__", None)
+ if qualname is not None:
+ cd["__qualname__"] = qualname
+
+ # __weakref__ is not writable.
+ state_attr_names = tuple(
+ an for an in self._attr_names if an != "__weakref__"
+ )
+
+ def slots_getstate(self):
+ """
+ Automatically created by attrs.
+ """
+ return tuple(getattr(self, name) for name in state_attr_names)
+
+ def slots_setstate(self, state):
+ """
+ Automatically created by attrs.
+ """
+ __bound_setattr = _obj_setattr.__get__(self, Attribute)
+ for name, value in zip(state_attr_names, state):
+ __bound_setattr(name, value)
+
+ # slots and frozen require __getstate__/__setstate__ to work
+ cd["__getstate__"] = slots_getstate
+ cd["__setstate__"] = slots_setstate
+
+ # Create new class based on old class and our methods.
+ cls = type(self._cls)(self._cls.__name__, self._cls.__bases__, cd)
+
+ # The following is a fix for
+ # https://github.com/python-attrs/attrs/issues/102. On Python 3,
+ # if a method mentions `__class__` or uses the no-arg super(), the
+ # compiler will bake a reference to the class in the method itself
+ # as `method.__closure__`. Since we replace the class with a
+ # clone, we rewrite these references so it keeps working.
+ for item in cls.__dict__.values():
+ if isinstance(item, (classmethod, staticmethod)):
+ # Class- and staticmethods hide their functions inside.
+ # These might need to be rewritten as well.
+ closure_cells = getattr(item.__func__, "__closure__", None)
+ else:
+ closure_cells = getattr(item, "__closure__", None)
+
+ if not closure_cells: # Catch None or the empty list.
+ continue
+ for cell in closure_cells:
+ if cell.cell_contents is self._cls:
+ set_closure_cell(cell, cls)
+
+ return cls
+
+ def add_repr(self, ns):
+ self._cls_dict["__repr__"] = self._add_method_dunders(
+ _make_repr(self._attrs, ns=ns)
+ )
+ return self
+
+ def add_str(self):
+ repr = self._cls_dict.get("__repr__")
+ if repr is None:
+ raise ValueError(
+ "__str__ can only be generated if a __repr__ exists."
+ )
+
+ def __str__(self):
+ return self.__repr__()
+
+ self._cls_dict["__str__"] = self._add_method_dunders(__str__)
+ return self
+
+ def make_unhashable(self):
+ self._cls_dict["__hash__"] = None
+ return self
+
+ def add_hash(self):
+ self._cls_dict["__hash__"] = self._add_method_dunders(
+ _make_hash(
+ self._attrs, frozen=self._frozen, cache_hash=self._cache_hash
+ )
+ )
+
+ return self
+
+ def add_init(self):
+ self._cls_dict["__init__"] = self._add_method_dunders(
+ _make_init(
+ self._attrs,
+ self._has_post_init,
+ self._frozen,
+ self._slots,
+ self._cache_hash,
+ self._base_attr_map,
+ )
+ )
+
+ return self
+
+ def add_cmp(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)
+ )
+
+ return self
+
+ def _add_method_dunders(self, method):
+ """
+ Add __module__ and __qualname__ to a *method* if possible.
+ """
+ try:
+ method.__module__ = self._cls.__module__
+ except AttributeError:
+ pass
+
+ try:
+ method.__qualname__ = ".".join(
+ (self._cls.__qualname__, method.__name__)
+ )
+ except AttributeError:
+ pass
+
+ return method
+
+
+def attrs(
+ maybe_cls=None,
+ these=None,
+ repr_ns=None,
+ repr=True,
+ cmp=True,
+ hash=None,
+ init=True,
+ slots=False,
+ frozen=False,
+ weakref_slot=True,
+ str=False,
+ auto_attribs=False,
+ kw_only=False,
+ cache_hash=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 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
+ 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`
+
+ :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
+ 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*!
+ :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 base class will be used (if base class 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 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 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/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
+ 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
+ works as expected.
+
+ Attributes annotated as :data:`typing.ClassVar` are **ignored**.
+
+ .. _`PEP 526`: https://www.python.org/dev/peps/pep-0526/
+ :param bool kw_only: Make all attributes keyword-only (Python 3+)
+ in the generated ``__init__`` (if ``init`` is ``False``, this
+ parameter is ignored).
+ :param bool cache_hash: Ensure that the object's hash code is computed
+ only once and stored on the object. If this is set to ``True``,
+ hashing must be either explicitly or implicitly enabled for this
+ class. If the hash code is cached, then no attributes of this
+ class which participate in hash code computation may be mutated
+ after object creation.
+
+
+ .. versionadded:: 16.0.0 *slots*
+ .. versionadded:: 16.1.0 *frozen*
+ .. versionadded:: 16.3.0 *str*
+ .. versionadded:: 16.3.0 Support for ``__attrs_post_init__``.
+ .. versionchanged:: 17.1.0
+ *hash* supports ``None`` as value which is also the default now.
+ .. versionadded:: 17.3.0 *auto_attribs*
+ .. versionchanged:: 18.1.0
+ If *these* is passed, no attributes are deleted from the class body.
+ .. versionchanged:: 18.1.0 If *these* is ordered, the order is retained.
+ .. 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
+ each other. ``__eq`` and ``__ne__`` never tried to compared subclasses
+ to each other.
+ .. versionadded:: 18.2.0 *kw_only*
+ .. versionadded:: 18.2.0 *cache_hash*
+ """
+
+ def wrap(cls):
+ if getattr(cls, "__class__", None) is None:
+ raise TypeError("attrs only works with new-style classes.")
+
+ builder = _ClassBuilder(
+ cls,
+ these,
+ slots,
+ frozen,
+ weakref_slot,
+ auto_attribs,
+ kw_only,
+ cache_hash,
+ )
+
+ if repr is True:
+ builder.add_repr(repr_ns)
+ if str is True:
+ builder.add_str()
+ if cmp is True:
+ builder.add_cmp()
+
+ 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):
+ 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):
+ builder.add_hash()
+ else:
+ if cache_hash:
+ raise TypeError(
+ "Invalid value for cache_hash. To use hash caching,"
+ " hashing must be either explicitly or implicitly "
+ "enabled."
+ )
+ builder.make_unhashable()
+
+ if init is True:
+ builder.add_init()
+ else:
+ if cache_hash:
+ raise TypeError(
+ "Invalid value for cache_hash. To use hash caching,"
+ " init must be True."
+ )
+
+ return builder.build_class()
+
+ # maybe_cls's type depends on the usage of the decorator. It's a class
+ # if it's used as `@attrs` but ``None`` if used as `@attrs()`.
+ if maybe_cls is None:
+ return wrap
+ else:
+ return wrap(maybe_cls)
+
+
+_attrs = attrs
+"""
+Internal alias so we can use it in functions that take an argument called
+*attrs*.
+"""
+
+
+if PY2:
+
+ def _has_frozen_base_class(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_base_class(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 _make_hash(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)
+ )
+
+ 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 = "<attrs generated hash %s>" % (sha1.hexdigest(),)
+ type_hash = hash(unique_filename)
+
+ method_lines = ["def __hash__(self):"]
+
+ def append_hash_computation_lines(prefix, indent):
+ """
+ Generate the code for actually computing the hash code.
+ Below this will either be returned directly or used to compute
+ a value which is then cached, depending on the value of cache_hash
+ """
+ method_lines.extend(
+ [indent + prefix + "hash((", indent + " %d," % (type_hash,)]
+ )
+
+ for a in attrs:
+ method_lines.append(indent + " self.%s," % a.name)
+
+ method_lines.append(indent + " ))")
+
+ if cache_hash:
+ method_lines.append(tab + "if self.%s is None:" % _hash_cache_field)
+ if frozen:
+ append_hash_computation_lines(
+ "object.__setattr__(self, '%s', " % _hash_cache_field, tab * 2
+ )
+ method_lines.append(tab * 2 + ")") # close __setattr__
+ else:
+ append_hash_computation_lines(
+ "self.%s = " % _hash_cache_field, tab * 2
+ )
+ method_lines.append(tab + "return self.%s" % _hash_cache_field)
+ else:
+ append_hash_computation_lines("return ", tab)
+
+ script = "\n".join(method_lines)
+ globs = {}
+ locs = {}
+ bytecode = compile(script, unique_filename, "exec")
+ eval(bytecode, globs, locs)
+
+ # 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,
+ )
+
+ return locs["__hash__"]
+
+
+def _add_hash(cls, attrs):
+ """
+ Add a hash method to *cls*.
+ """
+ cls.__hash__ = _make_hash(attrs, frozen=False, cache_hash=False)
+ return cls
+
+
+def __ne__(self, other):
+ """
+ Check equality and either forward a NotImplemented or return the result
+ negated.
+ """
+ result = self.__eq__(other)
+ if result is NotImplemented:
+ return NotImplemented
+
+ return not result
+
+
+WARNING_CMP_ISINSTANCE = (
+ "Comparision of subclasses using __%s__ is deprecated and will be removed "
+ "in 2019."
+)
+
+
+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 = "<attrs generated eq %s>" % (sha1.hexdigest(),)
+ lines = [
+ "def __eq__(self, other):",
+ " if other.__class__ is not self.__class__:",
+ " return NotImplemented",
+ ]
+ # We can't just do a big self.x = other.x and... clause due to
+ # irregularities like nan == nan is false but (nan,) == (nan,) is true.
+ if attrs:
+ lines.append(" return (")
+ others = [" ) == ("]
+ for a in attrs:
+ lines.append(" self.%s," % (a.name,))
+ others.append(" other.%s," % (a.name,))
+
+ lines += others + [" )"]
+ else:
+ lines.append(" return True")
+
+ script = "\n".join(lines)
+ globs = {}
+ locs = {}
+ bytecode = compile(script, unique_filename, "exec")
+ eval(bytecode, globs, locs)
+
+ # 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,
+ )
+ eq = locs["__eq__"]
+ ne = __ne__
+
+ def attrs_to_tuple(obj):
+ """
+ Save us some typing.
+ """
+ return _attrs_to_tuple(obj, attrs)
+
+ def __lt__(self, other):
+ """
+ Automatically created by attrs.
+ """
+ if isinstance(other, self.__class__):
+ if other.__class__ is not self.__class__:
+ warnings.warn(
+ WARNING_CMP_ISINSTANCE % ("lt",), DeprecationWarning
+ )
+ 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__):
+ if other.__class__ is not self.__class__:
+ warnings.warn(
+ WARNING_CMP_ISINSTANCE % ("le",), DeprecationWarning
+ )
+ 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__):
+ if other.__class__ is not self.__class__:
+ warnings.warn(
+ WARNING_CMP_ISINSTANCE % ("gt",), DeprecationWarning
+ )
+ 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__):
+ if other.__class__ is not self.__class__:
+ warnings.warn(
+ WARNING_CMP_ISINSTANCE % ("ge",), DeprecationWarning
+ )
+ return attrs_to_tuple(self) >= attrs_to_tuple(other)
+ else:
+ return NotImplemented
+
+ return eq, ne, __lt__, __le__, __gt__, __ge__
+
+
+def _add_cmp(cls, attrs=None):
+ """
+ Add comparison methods to *cls*.
+ """
+ if attrs is None:
+ attrs = cls.__attrs_attrs__
+
+ cls.__eq__, cls.__ne__, cls.__lt__, cls.__le__, cls.__gt__, cls.__ge__ = _make_cmp( # noqa
+ attrs
+ )
+
+ return cls
+
+
+_already_repring = threading.local()
+
+
+def _make_repr(attrs, ns):
+ """
+ Make a repr method for *attr_names* adding *ns* to the full name.
+ """
+ attr_names = tuple(a.name for a in attrs if a.repr)
+
+ def __repr__(self):
+ """
+ Automatically created by attrs.
+ """
+ try:
+ working_set = _already_repring.working_set
+ except AttributeError:
+ working_set = set()
+ _already_repring.working_set = working_set
+
+ if id(self) in working_set:
+ return "..."
+ 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__
+
+ # Since 'self' remains on the stack (i.e.: strongly referenced) for the
+ # duration of this call, it's safe to depend on id(...) stability, and
+ # not need to track the instance and therefore worry about properties
+ # like weakref- or hash-ability.
+ working_set.add(id(self))
+ try:
+ result = [class_name, "("]
+ first = True
+ for name in attr_names:
+ if first:
+ first = False
+ else:
+ result.append(", ")
+ result.extend((name, "=", repr(getattr(self, name, NOTHING))))
+ return "".join(result) + ")"
+ finally:
+ working_set.remove(id(self))
+
+ return __repr__
+
+
+def _add_repr(cls, ns=None, attrs=None):
+ """
+ Add a repr method to *cls*.
+ """
+ if attrs is None:
+ attrs = cls.__attrs_attrs__
+
+ cls.__repr__ = _make_repr(attrs, ns)
+ return cls
+
+
+def _make_init(attrs, post_init, frozen, slots, cache_hash, base_attr_map):
+ 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 = "<attrs generated init {0}>".format(sha1.hexdigest())
+
+ script, globs, annotations = _attrs_to_init_script(
+ attrs, frozen, slots, post_init, cache_hash, base_attr_map
+ )
+ 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)
+
+ # 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,
+ )
+
+ __init__ = locs["__init__"]
+ __init__.__annotations__ = annotations
+ return __init__
+
+
+def _add_init(cls, frozen):
+ """
+ Add a __init__ method to *cls*. If *frozen* is True, make it immutable.
+ """
+ cls.__init__ = _make_init(
+ cls.__attrs_attrs__,
+ getattr(cls, "__attrs_post_init__", False),
+ frozen,
+ _is_slot_cls(cls),
+ cache_hash=False,
+ base_attr_map={},
+ )
+ return cls
+
+
+def fields(cls):
+ """
+ Return 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 accessors) 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 fields_dict(cls):
+ """
+ Return an ordered dictionary of ``attrs`` attributes for a class, whose
+ keys are the attribute names.
+
+ :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: an ordered dict where keys are attribute names and values are
+ :class:`attr.Attribute`\\ s. This will be a :class:`dict` if it's
+ naturally ordered like on Python 3.6+ or an
+ :class:`~collections.OrderedDict` otherwise.
+
+ .. versionadded:: 18.1.0
+ """
+ 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 ordered_dict(((a.name, a) for a in 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 _is_slot_cls(cls):
+ return "__slots__" in cls.__dict__
+
+
+def _is_slot_attr(a_name, base_attr_map):
+ """
+ Check if the attribute name comes from a slot class.
+ """
+ return a_name in base_attr_map and _is_slot_cls(base_attr_map[a_name])
+
+
+def _attrs_to_init_script(
+ attrs, frozen, slots, post_init, cache_hash, base_attr_map
+):
+ """
+ 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 = []
+ any_slot_ancestors = any(
+ _is_slot_attr(a.name, base_attr_map) for a in attrs
+ )
+ if frozen is True:
+ if slots is True:
+ lines.append(
+ # Circumvent the __setattr__ descriptor to save one lookup per
+ # assignment.
+ # Note _setattr will be used again below if cache_hash is True
+ "_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_converter_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:
+ # Dict frozen classes assign directly to __dict__.
+ # But only if the attribute doesn't come from an ancestor slot
+ # class.
+ # Note _inst_dict will be used again below if cache_hash is True
+ lines.append("_inst_dict = self.__dict__")
+ if any_slot_ancestors:
+ 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):
+ if _is_slot_attr(attr_name, base_attr_map):
+ res = "_setattr('%(attr_name)s', %(value_var)s)" % {
+ "attr_name": attr_name,
+ "value_var": value_var,
+ }
+ else:
+ res = "_inst_dict['%(attr_name)s'] = %(value_var)s" % {
+ "attr_name": attr_name,
+ "value_var": value_var,
+ }
+ return res
+
+ def fmt_setter_with_converter(attr_name, value_var):
+ conv_name = _init_converter_pat.format(attr_name)
+ if _is_slot_attr(attr_name, base_attr_map):
+ tmpl = "_setattr('%(attr_name)s', %(c)s(%(value_var)s))"
+ else:
+ tmpl = "_inst_dict['%(attr_name)s'] = %(c)s(%(value_var)s)"
+ return tmpl % {
+ "attr_name": attr_name,
+ "value_var": value_var,
+ "c": conv_name,
+ }
+
+ else:
+ # Not frozen.
+ 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_converter_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 = []
+ kw_only_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 = {}
+ annotations = {"return": None}
+
+ 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.converter is not None:
+ lines.append(
+ fmt_setter_with_converter(
+ attr_name,
+ init_factory_name + "({0})".format(maybe_self),
+ )
+ )
+ conv_name = _init_converter_pat.format(a.name)
+ names_for_globals[conv_name] = a.converter
+ 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.converter is not None:
+ lines.append(
+ fmt_setter_with_converter(
+ attr_name,
+ "attr_dict['{attr_name}'].default".format(
+ attr_name=attr_name
+ ),
+ )
+ )
+ conv_name = _init_converter_pat.format(a.name)
+ names_for_globals[conv_name] = a.converter
+ 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:
+ arg = "{arg_name}=attr_dict['{attr_name}'].default".format(
+ arg_name=arg_name, attr_name=attr_name
+ )
+ if a.kw_only:
+ kw_only_args.append(arg)
+ else:
+ args.append(arg)
+ if a.converter is not None:
+ lines.append(fmt_setter_with_converter(attr_name, arg_name))
+ names_for_globals[
+ _init_converter_pat.format(a.name)
+ ] = a.converter
+ else:
+ lines.append(fmt_setter(attr_name, arg_name))
+ elif has_factory:
+ arg = "{arg_name}=NOTHING".format(arg_name=arg_name)
+ if a.kw_only:
+ kw_only_args.append(arg)
+ else:
+ args.append(arg)
+ lines.append(
+ "if {arg_name} is not NOTHING:".format(arg_name=arg_name)
+ )
+ init_factory_name = _init_factory_pat.format(a.name)
+ if a.converter 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_converter_pat.format(a.name)
+ ] = a.converter
+ 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:
+ if a.kw_only:
+ kw_only_args.append(arg_name)
+ else:
+ args.append(arg_name)
+ if a.converter is not None:
+ lines.append(fmt_setter_with_converter(attr_name, arg_name))
+ names_for_globals[
+ _init_converter_pat.format(a.name)
+ ] = a.converter
+ else:
+ lines.append(fmt_setter(attr_name, arg_name))
+
+ if a.init is True and a.converter is None and a.type is not None:
+ annotations[arg_name] = a.type
+
+ 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__()")
+
+ # because this is set only after __attrs_post_init is called, a crash
+ # will result if post-init tries to access the hash code. This seemed
+ # preferable to setting this beforehand, in which case alteration to
+ # field values during post-init combined with post-init accessing the
+ # hash code would result in silent bugs.
+ if cache_hash:
+ if frozen:
+ if slots:
+ # if frozen and slots, then _setattr defined above
+ init_hash_cache = "_setattr('%s', %s)"
+ else:
+ # if frozen and not slots, then _inst_dict defined above
+ init_hash_cache = "_inst_dict['%s'] = %s"
+ else:
+ init_hash_cache = "self.%s = %s"
+ lines.append(init_hash_cache % (_hash_cache_field, "None"))
+
+ args = ", ".join(args)
+ if kw_only_args:
+ if PY2:
+ raise PythonTooOldError(
+ "Keyword-only arguments only work on Python 3 and later."
+ )
+
+ args += "{leading_comma}*, {kw_only_args}".format(
+ leading_comma=", " if args else "",
+ kw_only_args=", ".join(kw_only_args),
+ )
+ return (
+ """\
+def __init__(self, {args}):
+ {lines}
+""".format(
+ args=args, lines="\n ".join(lines) if lines else "pass"
+ ),
+ names_for_globals,
+ annotations,
+ )
+
+
+class Attribute(object):
+ """
+ *Read-only* representation of an attribute.
+
+ :attribute name: The name of the attribute.
+
+ Plus *all* arguments of :func:`attr.ib`.
+
+ For the version history of the fields, see :func:`attr.ib`.
+ """
+
+ __slots__ = (
+ "name",
+ "default",
+ "validator",
+ "repr",
+ "cmp",
+ "hash",
+ "init",
+ "metadata",
+ "type",
+ "converter",
+ "kw_only",
+ )
+
+ def __init__(
+ self,
+ name,
+ default,
+ validator,
+ repr,
+ cmp,
+ hash,
+ init,
+ convert=None,
+ metadata=None,
+ type=None,
+ converter=None,
+ kw_only=False,
+ ):
+ # 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("hash", hash)
+ bound_setattr("init", init)
+ bound_setattr("converter", converter)
+ bound_setattr(
+ "metadata",
+ (
+ metadata_proxy(metadata)
+ if metadata
+ else _empty_metadata_singleton
+ ),
+ )
+ bound_setattr("type", type)
+ bound_setattr("kw_only", kw_only)
+
+ 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:
+ if type is None:
+ type = ca.type
+ elif ca.type is not None:
+ raise ValueError(
+ "Type annotation and type argument cannot both be present"
+ )
+ inst_dict = {
+ k: getattr(ca, k)
+ for k in Attribute.__slots__
+ if k
+ not in (
+ "name",
+ "validator",
+ "default",
+ "type",
+ "convert",
+ ) # exclude methods and deprecated alias
+ }
+ return cls(
+ name=name,
+ validator=ca._validator,
+ default=ca._default,
+ type=type,
+ **inst_dict
+ )
+
+ # Don't use attr.assoc since fields(Attribute) doesn't work
+ def _assoc(self, **changes):
+ """
+ Copy *self* and apply *changes*.
+ """
+ new = copy.copy(self)
+
+ new._setattrs(changes.items())
+
+ return new
+
+ # 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.
+ """
+ self._setattrs(zip(self.__slots__, state))
+
+ def _setattrs(self, name_values_pairs):
+ bound_setattr = _obj_setattr.__get__(self, Attribute)
+ for name, value in name_values_pairs:
+ 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__
+ if name != "convert" # XXX: remove once `convert` is gone
+]
+
+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",
+ "converter",
+ "type",
+ "kw_only",
+ )
+ __attrs_attrs__ = tuple(
+ Attribute(
+ name=name,
+ default=NOTHING,
+ validator=None,
+ repr=True,
+ cmp=True,
+ hash=True,
+ init=True,
+ kw_only=False,
+ )
+ for name in ("counter", "_default", "repr", "cmp", "hash", "init")
+ ) + (
+ Attribute(
+ name="metadata",
+ default=None,
+ validator=None,
+ repr=True,
+ cmp=True,
+ hash=False,
+ init=True,
+ kw_only=False,
+ ),
+ )
+ cls_counter = 0
+
+ def __init__(
+ self,
+ default,
+ validator,
+ repr,
+ cmp,
+ hash,
+ init,
+ converter,
+ metadata,
+ type,
+ kw_only,
+ ):
+ _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.converter = converter
+ self.metadata = metadata
+ self.type = type
+ self.kw_only = kw_only
+
+ 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))
+
+
+@attrs(slots=True, init=False, hash=True)
+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 = attrib()
+ takes_self = attrib()
+
+ 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.
+
+ If *attrs* is a list or an ordered dict (:class:`dict` on Python 3.6+,
+ :class:`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`
+
+ :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*
+ .. versionchanged:: 18.1.0 If *attrs* is ordered, the order is retained.
+ """
+ if isinstance(attrs, dict):
+ cls_dict = attrs
+ elif isinstance(attrs, (list, tuple)):
+ cls_dict = dict((a, attrib()) for a in attrs)
+ else:
+ raise TypeError("attrs argument must be a dict or a list.")
+
+ post_init = cls_dict.pop("__attrs_post_init__", None)
+ type_ = type(
+ name,
+ bases,
+ {} if post_init is None else {"__attrs_post_init__": post_init},
+ )
+ # For pickling to work, the __module__ variable needs to be set to the
+ # frame where the class is created. Bypass this step in environments where
+ # sys._getframe is not defined (Jython for example) or sys._getframe is not
+ # defined for arguments greater than 0 (IronPython).
+ try:
+ type_.__module__ = sys._getframe(1).f_globals.get(
+ "__name__", "__main__"
+ )
+ except (AttributeError, ValueError):
+ pass
+
+ return _attrs(these=cls_dict, **attributes_arguments)(type_)
+
+
+# These are required by within this module so we define them here and merely
+# import into .validators.
+
+
+@attrs(slots=True, hash=True)
+class _AndValidator(object):
+ """
+ Compose many validators to a single one.
+ """
+
+ _validators = attrib()
+
+ 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))
diff --git a/python/attr/converters.py b/python/attr/converters.py
new file mode 100644
index 0000000..37c4a07
--- /dev/null
+++ b/python/attr/converters.py
@@ -0,0 +1,78 @@
+"""
+Commonly useful converters.
+"""
+
+from __future__ import absolute_import, division, print_function
+
+from ._make import NOTHING, Factory
+
+
+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
+
+
+def default_if_none(default=NOTHING, factory=None):
+ """
+ A converter that allows to replace ``None`` values by *default* or the
+ 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
+ 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
+ ``takes_self=True``.
+
+ .. versionadded:: 18.2.0
+ """
+ if default is NOTHING and factory is None:
+ raise TypeError("Must pass either `default` or `factory`.")
+
+ if default is not NOTHING and factory is not None:
+ raise TypeError(
+ "Must pass either `default` or `factory` but not both."
+ )
+
+ if factory is not None:
+ default = Factory(factory)
+
+ if isinstance(default, Factory):
+ if default.takes_self:
+ raise ValueError(
+ "`takes_self` is not supported by default_if_none."
+ )
+
+ def default_if_none_converter(val):
+ if val is not None:
+ return val
+
+ return default.factory()
+
+ else:
+
+ def default_if_none_converter(val):
+ if val is not None:
+ return val
+
+ return default
+
+ return default_if_none_converter
diff --git a/python/attr/converters.pyi b/python/attr/converters.pyi
new file mode 100644
index 0000000..63b2a38
--- /dev/null
+++ b/python/attr/converters.pyi
@@ -0,0 +1,12 @@
+from typing import TypeVar, Optional, Callable, overload
+from . import _ConverterType
+
+_T = TypeVar("_T")
+
+def optional(
+ converter: _ConverterType[_T]
+) -> _ConverterType[Optional[_T]]: ...
+@overload
+def default_if_none(default: _T) -> _ConverterType[_T]: ...
+@overload
+def default_if_none(*, factory: Callable[[], _T]) -> _ConverterType[_T]: ...
diff --git a/python/attr/exceptions.py b/python/attr/exceptions.py
new file mode 100644
index 0000000..b12e41e
--- /dev/null
+++ b/python/attr/exceptions.py
@@ -0,0 +1,57 @@
+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
+ """
+
+
+class UnannotatedAttributeError(RuntimeError):
+ """
+ A class with ``auto_attribs=True`` has an ``attr.ib()`` without a type
+ annotation.
+
+ .. versionadded:: 17.3.0
+ """
+
+
+class PythonTooOldError(RuntimeError):
+ """
+ An ``attrs`` feature requiring a more recent python version has been used.
+
+ .. versionadded:: 18.2.0
+ """
diff --git a/python/attr/exceptions.pyi b/python/attr/exceptions.pyi
new file mode 100644
index 0000000..48fffcc
--- /dev/null
+++ b/python/attr/exceptions.pyi
@@ -0,0 +1,7 @@
+class FrozenInstanceError(AttributeError):
+ msg: str = ...
+
+class AttrsAttributeNotFoundError(ValueError): ...
+class NotAnAttrsClassError(ValueError): ...
+class DefaultAlreadySetError(RuntimeError): ...
+class UnannotatedAttributeError(RuntimeError): ...
diff --git a/python/attr/filters.py b/python/attr/filters.py
new file mode 100644
index 0000000..f1c69b8
--- /dev/null
+++ b/python/attr/filters.py
@@ -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_
diff --git a/python/attr/filters.pyi b/python/attr/filters.pyi
new file mode 100644
index 0000000..a618140
--- /dev/null
+++ b/python/attr/filters.pyi
@@ -0,0 +1,5 @@
+from typing import Union
+from . import Attribute, _FilterType
+
+def include(*what: Union[type, Attribute]) -> _FilterType: ...
+def exclude(*what: Union[type, Attribute]) -> _FilterType: ...
diff --git a/python/attr/py.typed b/python/attr/py.typed
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/python/attr/py.typed
diff --git a/python/attr/validators.py b/python/attr/validators.py
new file mode 100644
index 0000000..f12d0aa
--- /dev/null
+++ b/python/attr/validators.py
@@ -0,0 +1,170 @@
+"""
+Commonly useful validators.
+"""
+
+from __future__ import absolute_import, division, print_function
+
+from ._make import _AndValidator, and_, attrib, attrs
+
+
+__all__ = ["and_", "in_", "instance_of", "optional", "provides"]
+
+
+@attrs(repr=False, slots=True, hash=True)
+class _InstanceOfValidator(object):
+ type = attrib()
+
+ 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 performed 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)
+
+
+@attrs(repr=False, slots=True, hash=True)
+class _ProvidesValidator(object):
+ interface = attrib()
+
+ 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)
+
+
+@attrs(repr=False, slots=True, hash=True)
+class _OptionalValidator(object):
+ validator = attrib()
+
+ 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)
+
+
+@attrs(repr=False, slots=True, hash=True)
+class _InValidator(object):
+ options = attrib()
+
+ def __call__(self, inst, attr, value):
+ try:
+ in_options = value in self.options
+ except TypeError as e: # e.g. `1 in "abc"`
+ in_options = False
+
+ if not in_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)
diff --git a/python/attr/validators.pyi b/python/attr/validators.pyi
new file mode 100644
index 0000000..abbaedf
--- /dev/null
+++ b/python/attr/validators.pyi
@@ -0,0 +1,14 @@
+from typing import Container, List, Union, TypeVar, Type, Any, Optional, Tuple
+from . import _ValidatorType
+
+_T = TypeVar("_T")
+
+def instance_of(
+ type: Union[Tuple[Type[_T], ...], Type[_T]]
+) -> _ValidatorType[_T]: ...
+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]: ...