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
durin42 |
hg-reviewers |
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.
Automatic diff as part of commit; lint not applicable. |
Automatic diff as part of commit; unit tests not applicable. |
Path | Packages | |||
---|---|---|---|---|
A | M | mercurial/thirdparty/__init__.py | ||
A | M | mercurial/thirdparty/attr/LICENSE.txt (21 lines) | ||
A | M | mercurial/thirdparty/attr/__init__.py (71 lines) | ||
A | M | mercurial/thirdparty/attr/_compat.py (90 lines) | ||
A | M | mercurial/thirdparty/attr/_config.py (23 lines) | ||
A | M | mercurial/thirdparty/attr/_funcs.py (212 lines) | ||
A | M | mercurial/thirdparty/attr/_make.py (1059 lines) | ||
A | M | mercurial/thirdparty/attr/converters.py (24 lines) | ||
A | M | mercurial/thirdparty/attr/exceptions.py (39 lines) | ||
A | M | mercurial/thirdparty/attr/filters.py (52 lines) | ||
A | M | mercurial/thirdparty/attr/validators.py (166 lines) | ||
M | setup.py (2 lines) |
The MIT License (MIT) | |||||
Copyright (c) 2015 Hynek Schlawack | |||||
Permission is hereby granted, free of charge, to any person obtaining a copy | |||||
of this software and associated documentation files (the "Software"), to deal | |||||
in the Software without restriction, including without limitation the rights | |||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |||||
copies of the Software, and to permit persons to whom the Software is | |||||
furnished to do so, subject to the following conditions: | |||||
The above copyright notice and this permission notice shall be included in all | |||||
copies or substantial portions of the Software. | |||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | |||||
SOFTWARE. |
from __future__ import absolute_import, division, print_function | |||||
from ._funcs import ( | |||||
asdict, | |||||
assoc, | |||||
astuple, | |||||
evolve, | |||||
has, | |||||
) | |||||
from ._make import ( | |||||
Attribute, | |||||
Factory, | |||||
NOTHING, | |||||
attr, | |||||
attributes, | |||||
fields, | |||||
make_class, | |||||
validate, | |||||
) | |||||
from ._config import ( | |||||
get_run_validators, | |||||
set_run_validators, | |||||
) | |||||
from . import exceptions | |||||
from . import filters | |||||
from . import converters | |||||
from . import validators | |||||
__version__ = "17.2.0" | |||||
__title__ = "attrs" | |||||
__description__ = "Classes Without Boilerplate" | |||||
__uri__ = "http://www.attrs.org/" | |||||
__doc__ = __description__ + " <" + __uri__ + ">" | |||||
__author__ = "Hynek Schlawack" | |||||
__email__ = "hs@ox.cx" | |||||
__license__ = "MIT" | |||||
__copyright__ = "Copyright (c) 2015 Hynek Schlawack" | |||||
s = attrs = attributes | |||||
ib = attrib = attr | |||||
__all__ = [ | |||||
"Attribute", | |||||
"Factory", | |||||
"NOTHING", | |||||
"asdict", | |||||
"assoc", | |||||
"astuple", | |||||
"attr", | |||||
"attrib", | |||||
"attributes", | |||||
"attrs", | |||||
"converters", | |||||
"evolve", | |||||
"exceptions", | |||||
"fields", | |||||
"filters", | |||||
"get_run_validators", | |||||
"has", | |||||
"ib", | |||||
"make_class", | |||||
"s", | |||||
"set_run_validators", | |||||
"validate", | |||||
"validators", | |||||
] |
from __future__ import absolute_import, division, print_function | |||||
import sys | |||||
import types | |||||
PY2 = sys.version_info[0] == 2 | |||||
if PY2: | |||||
from UserDict import IterableUserDict | |||||
# We 'bundle' isclass instead of using inspect as importing inspect is | |||||
# fairly expensive (order of 10-15 ms for a modern machine in 2016) | |||||
def isclass(klass): | |||||
return isinstance(klass, (type, types.ClassType)) | |||||
# TYPE is used in exceptions, repr(int) is different on Python 2 and 3. | |||||
TYPE = "type" | |||||
def iteritems(d): | |||||
return d.iteritems() | |||||
def iterkeys(d): | |||||
return d.iterkeys() | |||||
# Python 2 is bereft of a read-only dict proxy, so we make one! | |||||
class ReadOnlyDict(IterableUserDict): | |||||
""" | |||||
Best-effort read-only dict wrapper. | |||||
""" | |||||
def __setitem__(self, key, val): | |||||
# We gently pretend we're a Python 3 mappingproxy. | |||||
raise TypeError("'mappingproxy' object does not support item " | |||||
"assignment") | |||||
def update(self, _): | |||||
# We gently pretend we're a Python 3 mappingproxy. | |||||
raise AttributeError("'mappingproxy' object has no attribute " | |||||
"'update'") | |||||
def __delitem__(self, _): | |||||
# We gently pretend we're a Python 3 mappingproxy. | |||||
raise TypeError("'mappingproxy' object does not support item " | |||||
"deletion") | |||||
def clear(self): | |||||
# We gently pretend we're a Python 3 mappingproxy. | |||||
raise AttributeError("'mappingproxy' object has no attribute " | |||||
"'clear'") | |||||
def pop(self, key, default=None): | |||||
# We gently pretend we're a Python 3 mappingproxy. | |||||
raise AttributeError("'mappingproxy' object has no attribute " | |||||
"'pop'") | |||||
def popitem(self): | |||||
# We gently pretend we're a Python 3 mappingproxy. | |||||
raise AttributeError("'mappingproxy' object has no attribute " | |||||
"'popitem'") | |||||
def setdefault(self, key, default=None): | |||||
# We gently pretend we're a Python 3 mappingproxy. | |||||
raise AttributeError("'mappingproxy' object has no attribute " | |||||
"'setdefault'") | |||||
def __repr__(self): | |||||
# Override to be identical to the Python 3 version. | |||||
return "mappingproxy(" + repr(self.data) + ")" | |||||
def metadata_proxy(d): | |||||
res = ReadOnlyDict() | |||||
res.data.update(d) # We blocked update, so we have to do it like this. | |||||
return res | |||||
else: | |||||
def isclass(klass): | |||||
return isinstance(klass, type) | |||||
TYPE = "class" | |||||
def iteritems(d): | |||||
return d.items() | |||||
def iterkeys(d): | |||||
return d.keys() | |||||
def metadata_proxy(d): | |||||
return types.MappingProxyType(dict(d)) |
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 |
from __future__ import absolute_import, division, print_function | |||||
import copy | |||||
from ._compat import iteritems | |||||
from ._make import NOTHING, fields, _obj_setattr | |||||
from .exceptions import AttrsAttributeNotFoundError | |||||
def asdict(inst, recurse=True, filter=None, dict_factory=dict, | |||||
retain_collection_types=False): | |||||
""" | |||||
Return the ``attrs`` attribute values of *inst* as a dict. | |||||
Optionally recurse into other ``attrs``-decorated classes. | |||||
:param inst: Instance of an ``attrs``-decorated class. | |||||
:param bool recurse: Recurse into classes that are also | |||||
``attrs``-decorated. | |||||
:param callable filter: A callable whose return code deteremines whether an | |||||
attribute or element is included (``True``) or dropped (``False``). Is | |||||
called with the :class:`attr.Attribute` as the first argument and the | |||||
value as the second argument. | |||||
:param callable dict_factory: A callable to produce dictionaries from. For | |||||
example, to produce ordered dictionaries instead of normal Python | |||||
dictionaries, pass in ``collections.OrderedDict``. | |||||
:param bool retain_collection_types: Do not convert to ``list`` when | |||||
encountering an attribute whose type is ``tuple`` or ``set``. Only | |||||
meaningful if ``recurse`` is ``True``. | |||||
:rtype: return type of *dict_factory* | |||||
:raise attr.exceptions.NotAnAttrsClassError: If *cls* is not an ``attrs`` | |||||
class. | |||||
.. versionadded:: 16.0.0 *dict_factory* | |||||
.. versionadded:: 16.1.0 *retain_collection_types* | |||||
""" | |||||
attrs = fields(inst.__class__) | |||||
rv = dict_factory() | |||||
for a in attrs: | |||||
v = getattr(inst, a.name) | |||||
if filter is not None and not filter(a, v): | |||||
continue | |||||
if recurse is True: | |||||
if has(v.__class__): | |||||
rv[a.name] = asdict(v, recurse=True, filter=filter, | |||||
dict_factory=dict_factory) | |||||
elif isinstance(v, (tuple, list, set)): | |||||
cf = v.__class__ if retain_collection_types is True else list | |||||
rv[a.name] = cf([ | |||||
asdict(i, recurse=True, filter=filter, | |||||
dict_factory=dict_factory) | |||||
if has(i.__class__) else i | |||||
for i in v | |||||
]) | |||||
elif isinstance(v, dict): | |||||
df = dict_factory | |||||
rv[a.name] = df(( | |||||
asdict(kk, dict_factory=df) if has(kk.__class__) else kk, | |||||
asdict(vv, dict_factory=df) if has(vv.__class__) else vv) | |||||
for kk, vv in iteritems(v)) | |||||
else: | |||||
rv[a.name] = v | |||||
else: | |||||
rv[a.name] = v | |||||
return rv | |||||
def astuple(inst, recurse=True, filter=None, tuple_factory=tuple, | |||||
retain_collection_types=False): | |||||
""" | |||||
Return the ``attrs`` attribute values of *inst* as a tuple. | |||||
Optionally recurse into other ``attrs``-decorated classes. | |||||
:param inst: Instance of an ``attrs``-decorated class. | |||||
:param bool recurse: Recurse into classes that are also | |||||
``attrs``-decorated. | |||||
:param callable filter: A callable whose return code determines whether an | |||||
attribute or element is included (``True``) or dropped (``False``). Is | |||||
called with the :class:`attr.Attribute` as the first argument and the | |||||
value as the second argument. | |||||
:param callable tuple_factory: A callable to produce tuples from. For | |||||
example, to produce lists instead of tuples. | |||||
:param bool retain_collection_types: Do not convert to ``list`` | |||||
or ``dict`` when encountering an attribute which type is | |||||
``tuple``, ``dict`` or ``set``. Only meaningful if ``recurse`` is | |||||
``True``. | |||||
:rtype: return type of *tuple_factory* | |||||
:raise attr.exceptions.NotAnAttrsClassError: If *cls* is not an ``attrs`` | |||||
class. | |||||
.. versionadded:: 16.2.0 | |||||
""" | |||||
attrs = fields(inst.__class__) | |||||
rv = [] | |||||
retain = retain_collection_types # Very long. :/ | |||||
for a in attrs: | |||||
v = getattr(inst, a.name) | |||||
if filter is not None and not filter(a, v): | |||||
continue | |||||
if recurse is True: | |||||
if has(v.__class__): | |||||
rv.append(astuple(v, recurse=True, filter=filter, | |||||
tuple_factory=tuple_factory, | |||||
retain_collection_types=retain)) | |||||
elif isinstance(v, (tuple, list, set)): | |||||
cf = v.__class__ if retain is True else list | |||||
rv.append(cf([ | |||||
astuple(j, recurse=True, filter=filter, | |||||
tuple_factory=tuple_factory, | |||||
retain_collection_types=retain) | |||||
if has(j.__class__) else j | |||||
for j in v | |||||
])) | |||||
elif isinstance(v, dict): | |||||
df = v.__class__ if retain is True else dict | |||||
rv.append(df( | |||||
( | |||||
astuple( | |||||
kk, | |||||
tuple_factory=tuple_factory, | |||||
retain_collection_types=retain | |||||
) if has(kk.__class__) else kk, | |||||
astuple( | |||||
vv, | |||||
tuple_factory=tuple_factory, | |||||
retain_collection_types=retain | |||||
) if has(vv.__class__) else vv | |||||
) | |||||
for kk, vv in iteritems(v))) | |||||
else: | |||||
rv.append(v) | |||||
else: | |||||
rv.append(v) | |||||
return rv if tuple_factory is list else tuple_factory(rv) | |||||
def has(cls): | |||||
""" | |||||
Check whether *cls* is a class with ``attrs`` attributes. | |||||
:param type cls: Class to introspect. | |||||
:raise TypeError: If *cls* is not a class. | |||||
:rtype: :class:`bool` | |||||
""" | |||||
return getattr(cls, "__attrs_attrs__", None) is not None | |||||
def assoc(inst, **changes): | |||||
""" | |||||
Copy *inst* and apply *changes*. | |||||
:param inst: Instance of a class with ``attrs`` attributes. | |||||
:param changes: Keyword changes in the new copy. | |||||
:return: A copy of inst with *changes* incorporated. | |||||
:raise attr.exceptions.AttrsAttributeNotFoundError: If *attr_name* couldn't | |||||
be found on *cls*. | |||||
:raise attr.exceptions.NotAnAttrsClassError: If *cls* is not an ``attrs`` | |||||
class. | |||||
.. deprecated:: 17.1.0 | |||||
Use :func:`evolve` instead. | |||||
""" | |||||
import warnings | |||||
warnings.warn("assoc is deprecated and will be removed after 2018/01.", | |||||
DeprecationWarning) | |||||
new = copy.copy(inst) | |||||
attrs = fields(inst.__class__) | |||||
for k, v in iteritems(changes): | |||||
a = getattr(attrs, k, NOTHING) | |||||
if a is NOTHING: | |||||
raise AttrsAttributeNotFoundError( | |||||
"{k} is not an attrs attribute on {cl}." | |||||
.format(k=k, cl=new.__class__) | |||||
) | |||||
_obj_setattr(new, k, v) | |||||
return new | |||||
def evolve(inst, **changes): | |||||
""" | |||||
Create a new instance, based on *inst* with *changes* applied. | |||||
:param inst: Instance of a class with ``attrs`` attributes. | |||||
:param changes: Keyword changes in the new copy. | |||||
:return: A copy of inst with *changes* incorporated. | |||||
:raise TypeError: If *attr_name* couldn't be found in the class | |||||
``__init__``. | |||||
:raise attr.exceptions.NotAnAttrsClassError: If *cls* is not an ``attrs`` | |||||
class. | |||||
.. versionadded:: 17.1.0 | |||||
""" | |||||
cls = inst.__class__ | |||||
attrs = fields(cls) | |||||
for a in attrs: | |||||
if not a.init: | |||||
continue | |||||
attr_name = a.name # To deal with private attributes. | |||||
init_name = attr_name if attr_name[0] != "_" else attr_name[1:] | |||||
if init_name not in changes: | |||||
changes[init_name] = getattr(inst, attr_name) | |||||
return cls(**changes) |
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)) |
""" | |||||
Commonly useful converters. | |||||
""" | |||||
from __future__ import absolute_import, division, print_function | |||||
def optional(converter): | |||||
""" | |||||
A converter that allows an attribute to be optional. An optional attribute | |||||
is one which can be set to ``None``. | |||||
:param callable converter: the converter that is used for non-``None`` | |||||
values. | |||||
.. versionadded:: 17.1.0 | |||||
""" | |||||
def optional_converter(val): | |||||
if val is None: | |||||
return None | |||||
return converter(val) | |||||
return optional_converter |
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 | |||||
""" |
""" | |||||
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_ |
""" | |||||
Commonly useful validators. | |||||
""" | |||||
from __future__ import absolute_import, division, print_function | |||||
from ._make import attr, attributes, and_, _AndValidator | |||||
__all__ = [ | |||||
"and_", | |||||
"in_", | |||||
"instance_of", | |||||
"optional", | |||||
"provides", | |||||
] | |||||
@attributes(repr=False, slots=True, hash=True) | |||||
class _InstanceOfValidator(object): | |||||
type = attr() | |||||
def __call__(self, inst, attr, value): | |||||
""" | |||||
We use a callable class to be able to change the ``__repr__``. | |||||
""" | |||||
if not isinstance(value, self.type): | |||||
raise TypeError( | |||||
"'{name}' must be {type!r} (got {value!r} that is a " | |||||
"{actual!r})." | |||||
.format(name=attr.name, type=self.type, | |||||
actual=value.__class__, value=value), | |||||
attr, self.type, value, | |||||
) | |||||
def __repr__(self): | |||||
return ( | |||||
"<instance_of validator for type {type!r}>" | |||||
.format(type=self.type) | |||||
) | |||||
def instance_of(type): | |||||
""" | |||||
A validator that raises a :exc:`TypeError` if the initializer is called | |||||
with a wrong type for this particular attribute (checks are perfomed using | |||||
:func:`isinstance` therefore it's also valid to pass a tuple of types). | |||||
:param type: The type to check for. | |||||
:type type: type or tuple of types | |||||
:raises TypeError: With a human readable error message, the attribute | |||||
(of type :class:`attr.Attribute`), the expected type, and the value it | |||||
got. | |||||
""" | |||||
return _InstanceOfValidator(type) | |||||
@attributes(repr=False, slots=True, hash=True) | |||||
class _ProvidesValidator(object): | |||||
interface = attr() | |||||
def __call__(self, inst, attr, value): | |||||
""" | |||||
We use a callable class to be able to change the ``__repr__``. | |||||
""" | |||||
if not self.interface.providedBy(value): | |||||
raise TypeError( | |||||
"'{name}' must provide {interface!r} which {value!r} " | |||||
"doesn't." | |||||
.format(name=attr.name, interface=self.interface, value=value), | |||||
attr, self.interface, value, | |||||
) | |||||
def __repr__(self): | |||||
return ( | |||||
"<provides validator for interface {interface!r}>" | |||||
.format(interface=self.interface) | |||||
) | |||||
def provides(interface): | |||||
""" | |||||
A validator that raises a :exc:`TypeError` if the initializer is called | |||||
with an object that does not provide the requested *interface* (checks are | |||||
performed using ``interface.providedBy(value)`` (see `zope.interface | |||||
<https://zopeinterface.readthedocs.io/en/latest/>`_). | |||||
:param zope.interface.Interface interface: The interface to check for. | |||||
:raises TypeError: With a human readable error message, the attribute | |||||
(of type :class:`attr.Attribute`), the expected interface, and the | |||||
value it got. | |||||
""" | |||||
return _ProvidesValidator(interface) | |||||
@attributes(repr=False, slots=True, hash=True) | |||||
class _OptionalValidator(object): | |||||
validator = attr() | |||||
def __call__(self, inst, attr, value): | |||||
if value is None: | |||||
return | |||||
self.validator(inst, attr, value) | |||||
def __repr__(self): | |||||
return ( | |||||
"<optional validator for {what} or None>" | |||||
.format(what=repr(self.validator)) | |||||
) | |||||
def optional(validator): | |||||
""" | |||||
A validator that makes an attribute optional. An optional attribute is one | |||||
which can be set to ``None`` in addition to satisfying the requirements of | |||||
the sub-validator. | |||||
:param validator: A validator (or a list of validators) that is used for | |||||
non-``None`` values. | |||||
:type validator: callable or :class:`list` of callables. | |||||
.. versionadded:: 15.1.0 | |||||
.. versionchanged:: 17.1.0 *validator* can be a list of validators. | |||||
""" | |||||
if isinstance(validator, list): | |||||
return _OptionalValidator(_AndValidator(validator)) | |||||
return _OptionalValidator(validator) | |||||
@attributes(repr=False, slots=True, hash=True) | |||||
class _InValidator(object): | |||||
options = attr() | |||||
def __call__(self, inst, attr, value): | |||||
if value not in self.options: | |||||
raise ValueError( | |||||
"'{name}' must be in {options!r} (got {value!r})" | |||||
.format(name=attr.name, options=self.options, value=value) | |||||
) | |||||
def __repr__(self): | |||||
return ( | |||||
"<in_ validator with options {options!r}>" | |||||
.format(options=self.options) | |||||
) | |||||
def in_(options): | |||||
""" | |||||
A validator that raises a :exc:`ValueError` if the initializer is called | |||||
with a value that does not belong in the options provided. The check is | |||||
performed using ``value in options``. | |||||
:param options: Allowed options. | |||||
:type options: list, tuple, :class:`enum.Enum`, ... | |||||
:raises ValueError: With a human readable error message, the attribute (of | |||||
type :class:`attr.Attribute`), the expected options, and the value it | |||||
got. | |||||
.. versionadded:: 17.1.0 | |||||
""" | |||||
return _InValidator(options) |
} | } | ||||
packages = ['mercurial', | packages = ['mercurial', | ||||
'mercurial.cext', | 'mercurial.cext', | ||||
'mercurial.cffi', | 'mercurial.cffi', | ||||
'mercurial.hgweb', | 'mercurial.hgweb', | ||||
'mercurial.httpclient', | 'mercurial.httpclient', | ||||
'mercurial.pure', | 'mercurial.pure', | ||||
'mercurial.thirdparty', | |||||
'mercurial.thirdparty.attr', | |||||
'hgext', 'hgext.convert', 'hgext.fsmonitor', | 'hgext', 'hgext.convert', 'hgext.fsmonitor', | ||||
'hgext.fsmonitor.pywatchman', 'hgext.highlight', | 'hgext.fsmonitor.pywatchman', 'hgext.highlight', | ||||
'hgext.largefiles', 'hgext.zeroconf', 'hgext3rd', | 'hgext.largefiles', 'hgext.zeroconf', 'hgext3rd', | ||||
'hgdemandimport'] | 'hgdemandimport'] | ||||
common_depends = ['mercurial/bitmanipulation.h', | common_depends = ['mercurial/bitmanipulation.h', | ||||
'mercurial/compat.h', | 'mercurial/compat.h', | ||||
'mercurial/cext/util.h'] | 'mercurial/cext/util.h'] |