Allow single attributes to be excluded
This commit is contained in:
parent
943d686509
commit
e7ebf4bf81
|
@ -7,6 +7,7 @@ import sys
|
|||
|
||||
PY3 = sys.version_info[0] == 3
|
||||
PY2 = sys.version_info[0] == 2
|
||||
PY26 = sys.version_info[0:2] == (2, 6)
|
||||
|
||||
|
||||
if PY2:
|
||||
|
|
|
@ -36,7 +36,8 @@ Sentinel to indicate the lack of a value when ``None`` is ambiguous.
|
|||
"""
|
||||
|
||||
|
||||
def attr(default=NOTHING, validator=None):
|
||||
def attr(default=NOTHING, validator=None, no_repr=False, no_cmp=False,
|
||||
no_hash=False, no_init=False):
|
||||
"""
|
||||
Create a new attribute on a class.
|
||||
|
||||
|
@ -56,10 +57,27 @@ def attr(default=NOTHING, validator=None):
|
|||
The return value is *not* inspected so the validator has to throw an
|
||||
exception itself.
|
||||
:type validator: callable
|
||||
|
||||
:param no_repr: Exclude this attribute when generating a ``__repr__``.
|
||||
:type no_repr: bool
|
||||
|
||||
:param no_cmp: Exclude this attribute when generating comparison methods
|
||||
(``__eq__`` et al).
|
||||
:type no_cmp: bool
|
||||
|
||||
:param no_hash: Exclude this attribute when generating a ``__hash__``.
|
||||
:type no_hash: bool
|
||||
|
||||
:param no_init: Exclude this attribute when generating a ``__init__``.
|
||||
:type no_init: bool
|
||||
"""
|
||||
return _CountingAttr(
|
||||
default=default,
|
||||
validator=validator,
|
||||
no_repr=no_repr,
|
||||
no_cmp=no_cmp,
|
||||
no_hash=no_hash,
|
||||
no_init=no_init,
|
||||
)
|
||||
|
||||
|
||||
|
@ -180,7 +198,7 @@ def _attrs_to_tuple(obj, attrs):
|
|||
|
||||
def _add_hash(cl, attrs=None):
|
||||
if attrs is None:
|
||||
attrs = cl.__attrs_attrs__
|
||||
attrs = [a for a in cl.__attrs_attrs__ if not a.no_hash]
|
||||
|
||||
def hash_(self):
|
||||
"""
|
||||
|
@ -194,7 +212,7 @@ def _add_hash(cl, attrs=None):
|
|||
|
||||
def _add_cmp(cl, attrs=None):
|
||||
if attrs is None:
|
||||
attrs = cl.__attrs_attrs__
|
||||
attrs = [a for a in cl.__attrs_attrs__ if not a.no_cmp]
|
||||
|
||||
def attrs_to_tuple(obj):
|
||||
"""
|
||||
|
@ -269,7 +287,7 @@ def _add_cmp(cl, attrs=None):
|
|||
|
||||
def _add_repr(cl, attrs=None):
|
||||
if attrs is None:
|
||||
attrs = cl.__attrs_attrs__
|
||||
attrs = [a for a in cl.__attrs_attrs__ if not a.no_repr]
|
||||
|
||||
def repr_(self):
|
||||
"""
|
||||
|
@ -285,7 +303,7 @@ def _add_repr(cl, attrs=None):
|
|||
|
||||
|
||||
def _add_init(cl):
|
||||
attrs = cl.__attrs_attrs__
|
||||
attrs = [a for a in cl.__attrs_attrs__ if not a.no_init]
|
||||
|
||||
# We cache the generated init methods for the same kinds of attributes.
|
||||
sha1 = hashlib.sha1()
|
||||
|
@ -371,7 +389,8 @@ class Attribute(object):
|
|||
:attribute validator: see :func:`attr.ib`
|
||||
"""
|
||||
_attributes = [
|
||||
"name", "default", "validator",
|
||||
"name", "default", "validator", "no_repr", "no_cmp", "no_hash",
|
||||
"no_init",
|
||||
] # we can't use ``attrs`` so we have to cheat a little.
|
||||
|
||||
def __init__(self, **kw):
|
||||
|
@ -392,7 +411,8 @@ class Attribute(object):
|
|||
if k != "name"))
|
||||
|
||||
|
||||
_a = [Attribute(name=name, default=NOTHING, validator=None)
|
||||
_a = [Attribute(name=name, default=NOTHING, validator=None, no_repr=False,
|
||||
no_cmp=False, no_hash=False, no_init=False)
|
||||
for name in Attribute._attributes]
|
||||
Attribute = _add_hash(
|
||||
_add_cmp(_add_repr(Attribute, attrs=_a), attrs=_a), attrs=_a
|
||||
|
@ -401,17 +421,22 @@ Attribute = _add_hash(
|
|||
|
||||
class _CountingAttr(object):
|
||||
__attrs_attrs__ = [
|
||||
Attribute(name=name, default=NOTHING, validator=None)
|
||||
Attribute(name=name, default=NOTHING, validator=None, no_repr=False,
|
||||
no_cmp=False, no_hash=False, no_init=False)
|
||||
for name
|
||||
in ("counter", "default",)
|
||||
in ("counter", "default", "no_repr", "no_cmp", "no_hash", "no_init",)
|
||||
]
|
||||
counter = 0
|
||||
|
||||
def __init__(self, default, validator):
|
||||
def __init__(self, default, validator, no_repr, no_cmp, no_hash, no_init):
|
||||
_CountingAttr.counter += 1
|
||||
self.counter = _CountingAttr.counter
|
||||
self.default = default
|
||||
self.validator = validator
|
||||
self.no_repr = no_repr
|
||||
self.no_cmp = no_cmp
|
||||
self.no_hash = no_hash
|
||||
self.no_init = no_init
|
||||
|
||||
|
||||
_CountingAttr = _add_cmp(_add_repr(_CountingAttr))
|
||||
|
|
|
@ -50,7 +50,7 @@ Core
|
|||
... class C(object):
|
||||
... x = attr.ib()
|
||||
>>> C.x
|
||||
Attribute(name='x', default=NOTHING, validator=None)
|
||||
Attribute(name='x', default=NOTHING, validator=None, no_repr=False, no_cmp=False, no_hash=False, no_init=False)
|
||||
|
||||
|
||||
.. autofunction:: attr.make_class
|
||||
|
@ -98,7 +98,7 @@ Helpers
|
|||
... x = attr.ib()
|
||||
... y = attr.ib()
|
||||
>>> attr.fields(C)
|
||||
[Attribute(name='x', default=NOTHING, validator=None), Attribute(name='y', default=NOTHING, validator=None)]
|
||||
[Attribute(name='x', default=NOTHING, validator=None, no_repr=False, no_cmp=False, no_hash=False, no_init=False), Attribute(name='y', default=NOTHING, validator=None, no_repr=False, no_cmp=False, no_hash=False, no_init=False)]
|
||||
|
||||
|
||||
.. autofunction:: attr.has
|
||||
|
|
|
@ -228,3 +228,14 @@ You can still have power over the attributes if you pass a dictionary of name: `
|
|||
42
|
||||
>>> i.y
|
||||
[]
|
||||
|
||||
Finally, you can exclude single attributes from certain methods:
|
||||
|
||||
.. doctest::
|
||||
|
||||
>>> @attr.s
|
||||
... class C(object):
|
||||
... user = attr.ib()
|
||||
... password = attr.ib(no_repr=True)
|
||||
>>> C("me", "s3kr3t")
|
||||
C(user='me')
|
||||
|
|
|
@ -2,11 +2,44 @@
|
|||
|
||||
from __future__ import absolute_import, division, print_function
|
||||
|
||||
from attr import Attribute
|
||||
from attr._make import NOTHING, make_class
|
||||
|
||||
def simple_attr(name):
|
||||
|
||||
def simple_class(add_cmp=False, add_repr=False, add_hash=False):
|
||||
"""
|
||||
Return a new simple class.
|
||||
"""
|
||||
return make_class(
|
||||
"C", ["a", "b"],
|
||||
add_cmp=add_cmp, add_repr=add_repr, add_hash=add_hash,
|
||||
add_init=True,
|
||||
)
|
||||
|
||||
|
||||
def simple_attr(name, default=NOTHING, validator=None, no_repr=False,
|
||||
no_cmp=False, no_hash=False, no_init=False):
|
||||
"""
|
||||
Return an attribute with a name and no other bells and whistles.
|
||||
"""
|
||||
from attr import Attribute
|
||||
from attr._make import NOTHING
|
||||
return Attribute(name=name, default=NOTHING, validator=None)
|
||||
return Attribute(
|
||||
name=name, default=default, validator=validator, no_repr=no_repr,
|
||||
no_cmp=no_cmp, no_hash=no_hash, no_init=no_init
|
||||
)
|
||||
|
||||
|
||||
class TestSimpleClass(object):
|
||||
"""
|
||||
Tests for the testing helper function `make_class`.
|
||||
"""
|
||||
def test_returns_class(self):
|
||||
"""
|
||||
Returns a class object.
|
||||
"""
|
||||
assert type is simple_class().__class__
|
||||
|
||||
def returns_distinct_classes(self):
|
||||
"""
|
||||
Each call returns a completely new class.
|
||||
"""
|
||||
assert simple_class() is not simple_class()
|
||||
|
|
|
@ -34,8 +34,11 @@ class TestDarkMagic(object):
|
|||
`attr.fields` works.
|
||||
"""
|
||||
assert [
|
||||
Attribute(name="x", default=foo, validator=None),
|
||||
Attribute(name="y", default=attr.Factory(list), validator=None),
|
||||
Attribute(name="x", default=foo, validator=None, no_repr=False,
|
||||
no_cmp=False, no_hash=False, no_init=False),
|
||||
Attribute(name="y", default=attr.Factory(list), validator=None,
|
||||
no_repr=False, no_cmp=False, no_hash=False,
|
||||
no_init=False),
|
||||
] == attr.fields(C2)
|
||||
|
||||
def test_asdict(self):
|
||||
|
@ -89,6 +92,8 @@ class TestDarkMagic(object):
|
|||
"""
|
||||
PC = attr.make_class("PC", ["a", "b"])
|
||||
assert [
|
||||
Attribute(name="a", default=NOTHING, validator=None),
|
||||
Attribute(name="b", default=NOTHING, validator=None),
|
||||
Attribute(name="a", default=NOTHING, validator=None, no_repr=False,
|
||||
no_cmp=False, no_hash=False, no_init=False),
|
||||
Attribute(name="b", default=NOTHING, validator=None, no_repr=False,
|
||||
no_cmp=False, no_hash=False, no_init=False),
|
||||
] == attr.fields(PC)
|
||||
|
|
|
@ -10,30 +10,20 @@ import copy
|
|||
|
||||
import pytest
|
||||
|
||||
from . import simple_attr
|
||||
from . import simple_attr, simple_class
|
||||
from attr._compat import PY26
|
||||
from attr._make import (
|
||||
Attribute,
|
||||
Factory,
|
||||
NOTHING,
|
||||
attr,
|
||||
make_class,
|
||||
_Nothing,
|
||||
_add_init,
|
||||
_add_repr,
|
||||
attr,
|
||||
make_class,
|
||||
)
|
||||
from attr.validators import instance_of
|
||||
|
||||
|
||||
def simple_class(add_cmp=False, add_repr=False, add_hash=False):
|
||||
"""
|
||||
Return a new simple class.
|
||||
"""
|
||||
return make_class(
|
||||
"C", ["a", "b"],
|
||||
add_cmp=add_cmp, add_repr=add_repr, add_hash=add_hash,
|
||||
add_init=True,
|
||||
)
|
||||
|
||||
CmpC = simple_class(add_cmp=True)
|
||||
ReprC = simple_class(add_repr=True)
|
||||
HashC = simple_class(add_hash=True)
|
||||
|
@ -45,27 +35,18 @@ class InitC(object):
|
|||
InitC = _add_init(InitC)
|
||||
|
||||
|
||||
class TestSimpleClass(object):
|
||||
"""
|
||||
Tests for the testing helper function `make_class`.
|
||||
"""
|
||||
def test_returns_class(self):
|
||||
"""
|
||||
Returns a class object.
|
||||
"""
|
||||
assert type is simple_class().__class__
|
||||
|
||||
def returns_distinct_classes(self):
|
||||
"""
|
||||
Each call returns a completely new class.
|
||||
"""
|
||||
assert simple_class() is not simple_class()
|
||||
|
||||
|
||||
class TestAddCmp(object):
|
||||
"""
|
||||
Tests for `_add_cmp`.
|
||||
"""
|
||||
def test_no_cmp(self):
|
||||
"""
|
||||
If `no_cmp` is set, ignore that attribute.
|
||||
"""
|
||||
C = make_class("C", {"a": attr(no_cmp=True), "b": attr()})
|
||||
|
||||
assert C(1, 2) == C(2, 2)
|
||||
|
||||
def test_equal(self):
|
||||
"""
|
||||
Equal objects are detected as equal.
|
||||
|
@ -168,6 +149,14 @@ class TestAddRepr(object):
|
|||
"""
|
||||
Tests for `_add_repr`.
|
||||
"""
|
||||
def test_no_repr(self):
|
||||
"""
|
||||
If `no_repr` is set, ignore that attribute.
|
||||
"""
|
||||
C = make_class("C", {"a": attr(no_repr=True), "b": attr()})
|
||||
|
||||
assert "C(b=2)" == repr(C(1, 2))
|
||||
|
||||
def test_repr(self):
|
||||
"""
|
||||
repr returns a sensible value.
|
||||
|
@ -192,6 +181,14 @@ class TestAddHash(object):
|
|||
"""
|
||||
Tests for `_add_hash`.
|
||||
"""
|
||||
def test_no_hash(self):
|
||||
"""
|
||||
If `no_hash` is set, ignore that attribute.
|
||||
"""
|
||||
C = make_class("C", {"a": attr(no_hash=True), "b": attr()})
|
||||
|
||||
assert hash(C(1, 2)) == hash(C(2, 2))
|
||||
|
||||
def test_hash(self):
|
||||
"""
|
||||
__hash__ returns different hashes for different values.
|
||||
|
@ -203,6 +200,17 @@ class TestAddInit(object):
|
|||
"""
|
||||
Tests for `_add_init`.
|
||||
"""
|
||||
def test_no_init(self):
|
||||
"""
|
||||
If `no_init` is set, ignore that attribute.
|
||||
"""
|
||||
C = make_class("C", {"a": attr(no_init=True), "b": attr()})
|
||||
with pytest.raises(TypeError) as e:
|
||||
C(a=1, b=2)
|
||||
|
||||
msg = e.value if PY26 else e.value.args[0]
|
||||
assert "__init__() got an unexpected keyword argument 'a'" == msg
|
||||
|
||||
def test_sets_attributes(self):
|
||||
"""
|
||||
The attributes are initialized using the passed keywords.
|
||||
|
@ -217,15 +225,9 @@ class TestAddInit(object):
|
|||
"""
|
||||
class C(object):
|
||||
__attrs_attrs__ = [
|
||||
Attribute(name="a",
|
||||
default=2,
|
||||
validator=None,),
|
||||
Attribute(name="b",
|
||||
default="hallo",
|
||||
validator=None,),
|
||||
Attribute(name="c",
|
||||
default=None,
|
||||
validator=None,),
|
||||
simple_attr(name="a", default=2),
|
||||
simple_attr(name="b", default="hallo"),
|
||||
simple_attr(name="c", default=None),
|
||||
]
|
||||
|
||||
C = _add_init(C)
|
||||
|
@ -243,12 +245,8 @@ class TestAddInit(object):
|
|||
|
||||
class C(object):
|
||||
__attrs_attrs__ = [
|
||||
Attribute(name="a",
|
||||
default=Factory(list),
|
||||
validator=None,),
|
||||
Attribute(name="b",
|
||||
default=Factory(D),
|
||||
validator=None,)
|
||||
simple_attr(name="a", default=Factory(list)),
|
||||
simple_attr(name="b", default=Factory(D)),
|
||||
]
|
||||
C = _add_init(C)
|
||||
i = C()
|
||||
|
|
|
@ -92,7 +92,8 @@ class TestTransformAttrs(object):
|
|||
assert (
|
||||
"No mandatory attributes allowed after an atribute with a "
|
||||
"default value or factory. Attribute in question: Attribute"
|
||||
"(name='y', default=NOTHING, validator=None)",
|
||||
"(name='y', default=NOTHING, validator=None, no_repr=False, "
|
||||
"no_cmp=False, no_hash=False, no_init=False)",
|
||||
) == e.value.args
|
||||
|
||||
|
||||
|
@ -202,7 +203,8 @@ class TestAttribute(object):
|
|||
"""
|
||||
with pytest.raises(TypeError) as e:
|
||||
Attribute(name="foo", default=NOTHING,
|
||||
factory=NOTHING, validator=None)
|
||||
factory=NOTHING, validator=None, no_repr=False,
|
||||
no_cmp=False, no_hash=False, no_init=False)
|
||||
assert ("Too many arguments.",) == e.value.args
|
||||
|
||||
|
||||
|
|
Loading…
Reference in New Issue