Skip to content

Commit

Permalink
Allow optional conversion (#173)
Browse files Browse the repository at this point in the history
Fixes #105
  • Loading branch information
adetokunbo authored and hynek committed May 10, 2017
1 parent d02b1d8 commit fdfd51e
Show file tree
Hide file tree
Showing 5 changed files with 83 additions and 0 deletions.
3 changes: 3 additions & 0 deletions CHANGELOG.rst
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,9 @@ Changes:
- Validators can now be defined conveniently inline by using the attribute as a decorator.
Check out the `examples <http://www.attrs.org/en/stable/examples.html#validators>`_ to see it in action!
`#143 <https://github.com/python-attrs/attrs/issues/143>`_
- Conversion can now be made optional using ``attr.converters.optional()``.
`#105 <https://github.com/python-attrs/attrs/issues/105>`_
`#173 <https://github.com/python-attrs/attrs/pull/173>`_
- ``attr.make_class()`` now accepts the keyword argument ``bases`` which allows for subclassing.
`#152 <https://github.com/python-attrs/attrs/pull/152>`_
- Metaclasses are now preserved with ``slots=True``.
Expand Down
18 changes: 18 additions & 0 deletions docs/api.rst
Original file line number Diff line number Diff line change
Expand Up @@ -284,6 +284,24 @@ Validators
C(x=None)


Converters
----------

.. autofunction:: attr.converters.optional

For example:

.. doctest::

>>> @attr.s
... class C(object):
... x = attr.ib(convert=attr.converters.optional(int))
>>> C(None)
C(x=None)
>>> C(42)
C(x=42)


Deprecated APIs
---------------

Expand Down
2 changes: 2 additions & 0 deletions src/attr/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
)
from . import exceptions
from . import filters
from . import converters
from . import validators


Expand Down Expand Up @@ -54,6 +55,7 @@
"attrib",
"attributes",
"attrs",
"converters",
"evolve",
"exceptions",
"fields",
Expand Down
24 changes: 24 additions & 0 deletions src/attr/converters.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
"""
Commonly useful converters.
"""

from __future__ import absolute_import, division, print_function


def optional(converter):
"""
A converter that allows an attribute to be optional. An optional attribute
is one which can be set to ``None``.
:param callable converter: the converter that is used for non-``None``
values.
.. versionadded:: 17.1.0
"""

def optional_converter(val):
if val is None:
return None
return converter(val)

return optional_converter
36 changes: 36 additions & 0 deletions tests/test_converters.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
"""
Tests for `attr.converters`.
"""

from __future__ import absolute_import

import pytest

from attr.converters import optional


class TestOptional(object):
"""
Tests for `optional`.
"""
def test_success_with_type(self):
"""
Wrapped converter is used as usual if value is not None.
"""
c = optional(int)
assert c("42") == 42

def test_success_with_none(self):
"""
Nothing happens if None.
"""
c = optional(int)
assert c(None) is None

def test_fail(self):
"""
Propagates the underlying conversion error when conversion fails.
"""
c = optional(int)
with pytest.raises(ValueError):
c("not_an_int")

0 comments on commit fdfd51e

Please sign in to comment.