Skip to content

Implement by_name #86

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

Merged
merged 6 commits into from
Sep 10, 2016
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.rst
Original file line number Diff line number Diff line change
@@ -6,7 +6,7 @@ attrs: Attributes Without Boilerplate
:target: http://attrs.readthedocs.io/en/stable/?badge=stable
:alt: Documentation Status

.. image:: https://travis-ci.org/hynek/attrs.svg
.. image:: https://travis-ci.org/hynek/attrs.svg?branch=master
:target: https://travis-ci.org/hynek/attrs
:alt: CI status

4 changes: 3 additions & 1 deletion docs/api.rst
Original file line number Diff line number Diff line change
@@ -103,6 +103,8 @@ Core


.. autoexception:: attr.exceptions.FrozenInstanceError
.. autoexception:: attr.exceptions.AttrsAttributeNotFoundError
.. autoexception:: attr.exceptions.NotAnAttrsClassError


.. _helpers:
@@ -161,6 +163,7 @@ Helpers

.. autofunction:: attr.filters.exclude

See :ref:`asdict` for examples.

.. autofunction:: assoc

@@ -181,7 +184,6 @@ Helpers
>>> i1 == i2
False


.. autofunction:: validate

For example:
8 changes: 6 additions & 2 deletions docs/examples.rst
Original file line number Diff line number Diff line change
@@ -160,6 +160,8 @@ Therefore ``@attr.s`` comes with the ``repr_ns`` option to set it manually:
On Python 3 it overrides the implicit detection.


.. _asdict:

Converting to Dictionaries
--------------------------

@@ -196,14 +198,16 @@ For the common case where you want to :func:`include <attr.filters.include>` or
... login = attr.ib()
... password = attr.ib()
... id = attr.ib()
>>> attr.asdict(User("jane", "s33kred", 42), filter=attr.filters.exclude(User.password, int))
>>> attr.asdict(User("jane", "s33kred", 42),
... filter=attr.filters.exclude(attr.fields(User).password, int))
{'login': 'jane'}
>>> @attr.s
... class C(object):
... x = attr.ib()
... y = attr.ib()
... z = attr.ib()
>>> attr.asdict(C("foo", "2", 3), filter=attr.filters.include(int, C.x))
>>> attr.asdict(C("foo", "2", 3),
... filter=attr.filters.include(int, attr.fields(C).x))
{'z': 3, 'x': 'foo'}


17 changes: 12 additions & 5 deletions src/attr/_funcs.py
Original file line number Diff line number Diff line change
@@ -4,6 +4,7 @@

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,
@@ -29,6 +30,9 @@ def asdict(inst, recurse=True, filter=None, dict_factory=dict,

: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*
"""
@@ -68,7 +72,6 @@ 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`
@@ -81,17 +84,21 @@ 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.
"""
new = copy.copy(inst)
attr_map = {a.name: a for a in new.__class__.__attrs_attrs__}
attrs = fields(inst.__class__)
for k, v in iteritems(changes):
a = attr_map.get(k, NOTHING)
a = getattr(attrs, k, NOTHING)
if a is NOTHING:
raise ValueError(
raise AttrsAttributeNotFoundError(
"{k} is not an attrs attribute on {cl}."
.format(k=k, cl=new.__class__)
)
11 changes: 6 additions & 5 deletions src/attr/_make.py
Original file line number Diff line number Diff line change
@@ -7,7 +7,7 @@

from . import _config
from ._compat import iteritems, isclass, iterkeys
from .exceptions import FrozenInstanceError
from .exceptions import FrozenInstanceError, NotAnAttrsClassError

# This is used at least twice, so cache it here.
_obj_setattr = object.__setattr__
@@ -501,17 +501,18 @@ def fields(cls):
:param type cls: Class to introspect.

:raise TypeError: If *cls* is not a class.
:raise ValueError: If *cls* is not an ``attrs`` class.
:raise attr.exceptions.NotAnAttrsClassError: If *cls* is not an ``attrs``
class.

:rtype: tuple of :class:`attr.Attribute`
"""
if not isclass(cls):
raise TypeError("Passed object must be a class.")
attrs = getattr(cls, "__attrs_attrs__", None)
if attrs is None:
raise ValueError("{cls!r} is not an attrs-decorated class.".format(
cls=cls
))
raise NotAnAttrsClassError(
"{cls!r} is not an attrs-decorated class.".format(cls=cls)
)
return attrs


16 changes: 16 additions & 0 deletions src/attr/exceptions.py
Original file line number Diff line number Diff line change
@@ -10,3 +10,19 @@ class FrozenInstanceError(AttributeError):
"""
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
"""
4 changes: 2 additions & 2 deletions src/attr/filters.py
Original file line number Diff line number Diff line change
@@ -23,7 +23,7 @@ def include(*what):
Whitelist *what*.

:param what: What to whitelist.
:type what: :class:`list` of :class:`type` or :class:`attr.Attribute` s.
:type what: :class:`list` of :class:`type` or :class:`attr.Attribute`\ s

:rtype: :class:`callable`
"""
@@ -40,7 +40,7 @@ def exclude(*what):
Blacklist *what*.

:param what: What to blacklist.
:type what: :class:`list` of classes or :class:`attr.Attribute` s.
:type what: :class:`list` of classes or :class:`attr.Attribute`\ s.

:rtype: :class:`callable`
"""
26 changes: 13 additions & 13 deletions tests/test_filters.py
Original file line number Diff line number Diff line change
@@ -6,7 +6,7 @@

import pytest

from attr._make import attributes, attr
from attr._make import attributes, attr, fields
from attr.filters import _split_what, include, exclude


@@ -37,28 +37,28 @@ class TestInclude(object):
@pytest.mark.parametrize("incl,value", [
((int,), 42),
((str,), "hello"),
((str, C.a), 42),
((str, C.b), "hello"),
((str, fields(C).a), 42),
((str, fields(C).b), "hello"),
])
def test_allow(self, incl, value):
"""
Return True if a class or attribute is whitelisted.
"""
i = include(*incl)
assert i(C.a, value) is True
assert i(fields(C).a, value) is True

@pytest.mark.parametrize("incl,value", [
((str,), 42),
((int,), "hello"),
((str, C.b), 42),
((int, C.b), "hello"),
((str, fields(C).b), 42),
((int, fields(C).b), "hello"),
])
def test_drop_class(self, incl, value):
"""
Return False on non-whitelisted classes and attributes.
"""
i = include(*incl)
assert i(C.a, value) is False
assert i(fields(C).a, value) is False


class TestExclude(object):
@@ -68,25 +68,25 @@ class TestExclude(object):
@pytest.mark.parametrize("excl,value", [
((str,), 42),
((int,), "hello"),
((str, C.b), 42),
((int, C.b), "hello"),
((str, fields(C).b), 42),
((int, fields(C).b), "hello"),
])
def test_allow(self, excl, value):
"""
Return True if class or attribute is not blacklisted.
"""
e = exclude(*excl)
assert e(C.a, value) is True
assert e(fields(C).a, value) is True

@pytest.mark.parametrize("excl,value", [
((int,), 42),
((str,), "hello"),
((str, C.a), 42),
((str, C.b), "hello"),
((str, fields(C).a), 42),
((str, fields(C).b), "hello"),
])
def test_drop_class(self, excl, value):
"""
Return True on non-blacklisted classes and attributes.
"""
e = exclude(*excl)
assert e(C.a, value) is False
assert e(fields(C).a, value) is False
3 changes: 2 additions & 1 deletion tests/test_funcs.py
Original file line number Diff line number Diff line change
@@ -22,6 +22,7 @@
attributes,
fields,
)
from attr.exceptions import AttrsAttributeNotFoundError

MAPPING_TYPES = (dict, OrderedDict)
SEQUENCE_TYPES = (list, tuple)
@@ -230,7 +231,7 @@ def test_unknown(self, C):
Wanting to change an unknown attribute raises a ValueError.
"""
# No generated class will have a four letter attribute.
with pytest.raises(ValueError) as e:
with pytest.raises(AttrsAttributeNotFoundError) as e:
assoc(C(), aaaa=2)
assert (
"aaaa is not an attrs attribute on {cls!r}.".format(cls=C),
3 changes: 2 additions & 1 deletion tests/test_make.py
Original file line number Diff line number Diff line change
@@ -22,6 +22,7 @@
validate,
Factory,
)
from attr.exceptions import NotAnAttrsClassError

from .utils import simple_attr, simple_attrs, simple_classes

@@ -363,7 +364,7 @@ def test_handler_non_attrs_class(self, C):
"""
Raises `ValueError` if passed a non-``attrs`` instance.
"""
with pytest.raises(ValueError) as e:
with pytest.raises(NotAnAttrsClassError) as e:
fields(object)
assert (
"{o!r} is not an attrs-decorated class.".format(o=object)
1 change: 0 additions & 1 deletion tox.ini
Original file line number Diff line number Diff line change
@@ -12,7 +12,6 @@ deps = -rdev-requirements.txt
commands = pypy -m pytest {posargs}



[testenv:flake8]
basepython = python3.5
deps = flake8