Compatibility fixes. (#193)

* Restore hash to validators.

* Restore Attribute.default and Attribute.validator.

* _AndValidator is hashable now. Test validator hashability.

* Add in_ to validators.__all__.
This commit is contained in:
Tin Tvrtković 2017-05-19 20:31:00 +02:00 committed by Hynek Schlawack
parent 616f24491d
commit 82ea73cb47
6 changed files with 40 additions and 22 deletions

View File

@ -24,7 +24,9 @@ Deprecations:
Changes:
^^^^^^^^
*none*
- Validators are hashable again.
Note that validators may become frozen in the future, pending availability of no-overhead frozen classes.
`#192 <https://github.com/python-attrs/attrs/issues/192>`_
----

View File

@ -818,14 +818,14 @@ class Attribute(object):
"convert", "metadata",
)
def __init__(self, name, _default, _validator, repr, cmp, hash, init,
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("default", default)
bound_setattr("validator", validator)
bound_setattr("repr", repr)
bound_setattr("cmp", cmp)
bound_setattr("hash", hash)
@ -842,12 +842,13 @@ class Attribute(object):
inst_dict = {
k: getattr(ca, k)
for k
in Attribute.__slots__ + ("_validator", "_default")
if k != "name" and k not in (
"validator", "default",
in Attribute.__slots__
if k not in (
"name", "validator", "default",
) # exclude methods
}
return cls(name=name, **inst_dict)
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):
@ -871,7 +872,7 @@ class Attribute(object):
_empty_metadata_singleton)
_a = [Attribute(name=name, _default=NOTHING, _validator=None,
_a = [Attribute(name=name, default=NOTHING, validator=None,
repr=True, cmp=True, hash=(name != "metadata"), init=True)
for name in Attribute.__slots__]
@ -892,12 +893,12 @@ class _CountingAttr(object):
__slots__ = ("counter", "_default", "repr", "cmp", "hash", "init",
"metadata", "_validator", "convert")
__attrs_attrs__ = tuple(
Attribute(name=name, _default=NOTHING, _validator=None,
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,
Attribute(name="metadata", default=None, validator=None,
repr=True, cmp=True, hash=False, init=True),
)
cls_counter = 0
@ -1015,7 +1016,7 @@ def make_class(name, attrs, bases=(object,), **attributes_arguments):
# import into .validators.
@attributes(slots=True)
@attributes(slots=True, hash=True)
class _AndValidator(object):
"""
Compose many validators to a single one.

View File

@ -9,13 +9,14 @@ from ._make import attr, attributes, and_, _AndValidator
__all__ = [
"and_",
"in_",
"instance_of",
"optional",
"provides",
]
@attributes(repr=False, slots=True)
@attributes(repr=False, slots=True, hash=True)
class _InstanceOfValidator(object):
type = attr()
@ -55,7 +56,7 @@ def instance_of(type):
return _InstanceOfValidator(type)
@attributes(repr=False, slots=True)
@attributes(repr=False, slots=True, hash=True)
class _ProvidesValidator(object):
interface = attr()
@ -94,7 +95,7 @@ def provides(interface):
return _ProvidesValidator(interface)
@attributes(repr=False, slots=True)
@attributes(repr=False, slots=True, hash=True)
class _OptionalValidator(object):
validator = attr()
@ -129,7 +130,7 @@ def optional(validator):
return _OptionalValidator(validator)
@attributes(repr=False, slots=True)
@attributes(repr=False, slots=True, hash=True)
class _InValidator(object):
options = attr()

View File

@ -109,9 +109,9 @@ class TestDarkMagic(object):
`attr.fields` works.
"""
assert (
Attribute(name="x", _default=foo, _validator=None,
Attribute(name="x", default=foo, validator=None,
repr=True, cmp=True, hash=None, init=True),
Attribute(name="y", _default=attr.Factory(list), _validator=None,
Attribute(name="y", default=attr.Factory(list), validator=None,
repr=True, cmp=True, hash=None, init=True),
) == attr.fields(cls)
@ -158,9 +158,9 @@ class TestDarkMagic(object):
"""
PC = attr.make_class("PC", ["a", "b"], slots=slots, frozen=frozen)
assert (
Attribute(name="a", _default=NOTHING, _validator=None,
Attribute(name="a", default=NOTHING, validator=None,
repr=True, cmp=True, hash=None, init=True),
Attribute(name="b", _default=NOTHING, _validator=None,
Attribute(name="b", default=NOTHING, validator=None,
repr=True, cmp=True, hash=None, init=True),
) == attr.fields(PC)

View File

@ -7,6 +7,7 @@ from __future__ import absolute_import, division, print_function
import pytest
import zope.interface
from attr import validators as validator_module, has
from attr.validators import and_, instance_of, provides, optional, in_
from attr._compat import TYPE
from attr._make import attributes, attr
@ -248,3 +249,16 @@ class TestIn_(object):
assert(
("<in_ validator with options [3, 4, 5]>")
) == repr(v)
def test_hashability():
"""
Validator classes are hashable.
"""
for obj_name in dir(validator_module):
obj = getattr(validator_module, obj_name)
if not has(obj):
continue
hash_func = getattr(obj, '__hash__', None)
assert hash_func is not None
assert hash_func is not object.__hash__

View File

@ -35,7 +35,7 @@ def simple_attr(name, default=NOTHING, validator=None, repr=True,
Return an attribute with a name and no other bells and whistles.
"""
return Attribute(
name=name, _default=default, _validator=validator, repr=repr,
name=name, default=default, validator=validator, repr=repr,
cmp=cmp, hash=hash, init=init
)
@ -166,7 +166,7 @@ def simple_attrs_with_metadata(draw):
vals = st.booleans() | st.binary() | st.integers() | st.text()
metadata = draw(st.dictionaries(keys=keys, values=vals))
return attr.ib(c_attr.default, c_attr._validator, c_attr.repr,
return attr.ib(c_attr._default, c_attr._validator, c_attr.repr,
c_attr.cmp, c_attr.hash, c_attr.init, c_attr.convert,
metadata)