Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Make _CountingAttr empty metadata unique #280

Merged
2 changes: 2 additions & 0 deletions changelog.d/280.change.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
``attrib`` now defaults to a unique empty ``dict`` instance for the returned ``_CountingAttr`` instead of sharing a common empty ``dict`` for all.
The singleton empty ``dict`` is still enforced by ``@attr.s``.
4 changes: 3 additions & 1 deletion src/attr/_make.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ def __hash__(self):

def attrib(default=NOTHING, validator=None,
repr=True, cmp=True, hash=None, init=True,
convert=None, metadata={}, type=None):
convert=None, metadata=None, type=None):
"""
Create a new attribute on a class.

Expand Down Expand Up @@ -134,6 +134,8 @@ def attrib(default=NOTHING, validator=None,
raise TypeError(
"Invalid value for hash. Must be True, False, or None."
)
if metadata is None:

This comment was marked as spam.

metadata = {}
return _CountingAttr(
default=default,
validator=validator,
Expand Down
31 changes: 30 additions & 1 deletion tests/test_make.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
from __future__ import absolute_import, division, print_function

import inspect
import itertools
import sys

from operator import attrgetter
Expand All @@ -26,7 +27,7 @@

from .utils import (
gen_attr_names, list_of_attrs, simple_attr, simple_attrs,
simple_attrs_without_metadata, simple_classes
simple_attrs_with_metadata, simple_attrs_without_metadata, simple_classes
)


Expand Down Expand Up @@ -823,6 +824,34 @@ def test_empty_metadata_singleton(self, list_of_attrs):
for a in fields(C)[1:]:
assert a.metadata is fields(C)[0].metadata

@given(lists(simple_attrs_without_metadata, min_size=2, max_size=5))
def test_empty_countingattr_metadata_independent(self, list_of_attrs):
"""
All empty metadata attributes are independent before ``@attr.s``.
"""
for x, y in itertools.combinations(list_of_attrs, 2):
assert x.metadata is not y.metadata

@given(lists(simple_attrs_with_metadata(), min_size=2, max_size=5))
def test_not_none_metadata(self, list_of_attrs):
"""
Non-empty metadata attributes exist as fields after ``@attr.s``.
"""
C = make_class("C", dict(zip(gen_attr_names(), list_of_attrs)))

assert len(fields(C)) > 0

for cls_a, raw_a in zip(fields(C), list_of_attrs):
assert cls_a.metadata != {}
assert cls_a.metadata == raw_a.metadata

def test_not_none_metadata_force_coverage(self):
"""
Force coverage of metadata is not None case even though other tests
should do so anyways.
"""
attr.ib(metadata={})


class TestClassBuilder(object):
"""
Expand Down
6 changes: 4 additions & 2 deletions tests/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -145,7 +145,7 @@ def ordereddict_of_class(tup):
attrs_and_classes.map(ordereddict_of_class))


bare_attrs = st.just(attr.ib(default=None))
bare_attrs = st.builds(attr.ib, default=st.none())
int_attrs = st.integers().map(lambda i: attr.ib(default=i))
str_attrs = st.text().map(lambda s: attr.ib(default=s))
float_attrs = st.floats().map(lambda f: attr.ib(default=f))
Expand All @@ -164,7 +164,9 @@ def simple_attrs_with_metadata(draw):
c_attr = draw(simple_attrs)
keys = st.booleans() | st.binary() | st.integers() | st.text()
vals = st.booleans() | st.binary() | st.integers() | st.text()
metadata = draw(st.dictionaries(keys=keys, values=vals))
metadata = draw(st.dictionaries(
keys=keys, values=vals, min_size=1, max_size=5))
print('metadata', metadata)

This comment was marked as spam.


return attr.ib(c_attr._default, c_attr._validator, c_attr.repr,
c_attr.cmp, c_attr.hash, c_attr.init, c_attr.convert,
Expand Down