181 lines
5.7 KiB
Python
181 lines
5.7 KiB
Python
# -*- coding: utf-8 -*-
|
|
|
|
from __future__ import absolute_import, division, print_function
|
|
|
|
|
|
from ._dunders import (
|
|
NOTHING,
|
|
_add_cmp,
|
|
_add_hash,
|
|
_add_init,
|
|
_add_repr,
|
|
)
|
|
|
|
|
|
class Attribute(object):
|
|
"""
|
|
*Read-only* representation of an attribute.
|
|
|
|
:attribute name: The name of the attribute.
|
|
:attribute default_value: see :func:`attr.ib`
|
|
:attribute default_factory: see :func:`attr.ib`
|
|
:attribute validator: see :func:`attr.ib`
|
|
"""
|
|
_attributes = [
|
|
"name", "default_value", "default_factory", "validator",
|
|
] # we can't use ``attrs`` so we have to cheat a little.
|
|
|
|
def __init__(self, **kw):
|
|
try:
|
|
for a in Attribute._attributes:
|
|
setattr(self, a, kw[a])
|
|
except KeyError:
|
|
raise TypeError("Missing argument '{arg}'.".format(arg=a))
|
|
|
|
@classmethod
|
|
def from_counting_attr(cl, name, ca):
|
|
return cl(name=name,
|
|
**dict((k, getattr(ca, k))
|
|
for k
|
|
in Attribute._attributes
|
|
if k != "name"))
|
|
|
|
|
|
_a = [Attribute(name=name, default_value=NOTHING, default_factory=NOTHING,
|
|
validator=None)
|
|
for name in Attribute._attributes]
|
|
Attribute = _add_hash(
|
|
_add_cmp(_add_repr(Attribute, attrs=_a), attrs=_a), attrs=_a
|
|
)
|
|
|
|
|
|
class _CountingAttr(object):
|
|
__attrs_attrs__ = [
|
|
Attribute(name=name, default_value=NOTHING, default_factory=NOTHING,
|
|
validator=None)
|
|
for name
|
|
in ("counter", "default_value", "default_factory",)
|
|
]
|
|
counter = 0
|
|
|
|
def __init__(self, default_value, default_factory, validator):
|
|
_CountingAttr.counter += 1
|
|
self.counter = _CountingAttr.counter
|
|
self.default_value = default_value
|
|
self.default_factory = default_factory
|
|
self.validator = validator
|
|
|
|
|
|
_CountingAttr = _add_cmp(_add_repr(_CountingAttr))
|
|
|
|
|
|
def _make_attr(default_value=NOTHING, default_factory=NOTHING, validator=None):
|
|
"""
|
|
Create a new attribute on a class.
|
|
|
|
.. warning::
|
|
|
|
Does nothing unless the class is also decorated with :func:`attr.s`!
|
|
|
|
:param default_value: Value that is used if an ``attrs``-generated
|
|
``__init__`` is used and no value is passed while instantiating.
|
|
:type default_value: Any value.
|
|
|
|
:param default_factory: :func:`callable` that is called to obtain
|
|
a default value if an ``attrs``-generated ``__init__`` is used and no
|
|
value is passed while instantiating.
|
|
:type default_factory: callable
|
|
|
|
:param validator: :func:`callable` that is called within
|
|
``attrs``-generated ``__init__`` methods with the :class:`Attribute` as
|
|
the first parameter and the passed value as the second parameter.
|
|
|
|
The return value is *not* inspected so the validator has to throw an
|
|
exception itself.
|
|
:type validator: callable
|
|
"""
|
|
if default_value is not NOTHING and default_factory is not NOTHING:
|
|
raise ValueError(
|
|
"Specifying both default_value and default_factory is "
|
|
"ambiguous."
|
|
)
|
|
|
|
return _CountingAttr(
|
|
default_value=default_value,
|
|
default_factory=default_factory,
|
|
validator=validator,
|
|
)
|
|
|
|
|
|
def _transform_attrs(cl):
|
|
"""
|
|
Transforms all `_CountingAttr`s on a class into `Attribute`s and saves the
|
|
list in `__attrs_attrs__`.
|
|
"""
|
|
cl.__attrs_attrs__ = []
|
|
for attr_name, ca in sorted(
|
|
((name, attr) for name, attr
|
|
in cl.__dict__.items()
|
|
if isinstance(attr, _CountingAttr)),
|
|
key=lambda e: e[1].counter
|
|
):
|
|
a = Attribute.from_counting_attr(name=attr_name, ca=ca)
|
|
cl.__attrs_attrs__.append(a)
|
|
setattr(cl, attr_name, a)
|
|
|
|
|
|
def _add_methods(maybe_cl=None, add_repr=True, add_cmp=True, add_hash=True,
|
|
add_init=True):
|
|
"""
|
|
A class decorator that adds `dunder
|
|
<https://wiki.python.org/moin/DunderAlias>`_\ -methods according to the
|
|
specified attributes using :func:`attr.ib`.
|
|
|
|
:param add_repr: Create a ``__repr__`` method with a human readable
|
|
represantation of ``attrs`` attributes..
|
|
:type add_repr: bool
|
|
|
|
:param add_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*!
|
|
:type add_cmp: bool
|
|
|
|
:param add_hash: Add a ``__hash__`` method that returns the :func:`hash` of
|
|
a tuple of all ``attrs`` attribute values.
|
|
:type add_hash: bool
|
|
|
|
:param add_init: Add a ``__init__`` method that initialiazes the ``attrs``
|
|
attributes. Leading underscores are stripped for the argument name:.
|
|
|
|
.. doctest::
|
|
|
|
>>> import attr
|
|
>>> @attr.s
|
|
... class C(object):
|
|
... _private = attr.ib()
|
|
>>> C(private=42)
|
|
C(_private=42)
|
|
:type add_init: bool
|
|
"""
|
|
# attrs_or class type depends on the usage of the decorator. It's a class
|
|
# if it's used as `@_add_methods` but ``None`` (or a value passed) if used
|
|
# as `@_add_methods()`.
|
|
if isinstance(maybe_cl, type):
|
|
_transform_attrs(maybe_cl)
|
|
return _add_init(_add_hash(_add_cmp(_add_repr(maybe_cl))))
|
|
else:
|
|
def wrap(cl):
|
|
_transform_attrs(cl)
|
|
if add_repr is True:
|
|
cl = _add_repr(cl)
|
|
if add_cmp is True:
|
|
cl = _add_cmp(cl)
|
|
if add_hash is True:
|
|
cl = _add_hash(cl)
|
|
if add_init is True:
|
|
cl = _add_init(cl)
|
|
return cl
|
|
|
|
return wrap
|