Skip to content

Commit

Permalink
Inline distutils.util.strtobool in tests (python-attrs#813)
Browse files Browse the repository at this point in the history
`distutils` is deprecated in Python 3.10 and slated for removal in
Python 3.12. Fortunately, `attrs` only uses `distutils` once and it's
trivial to remove.

As suggested by @sscherfke, add the `to_bool` converter to
`converters.py`.

Closes python-attrs#813

Co-authored-by: Stefan Scherfke <stefan@sofa-rockers.org>
  • Loading branch information
Rebecca Turner and sscherfke committed Jul 7, 2021
1 parent 3858063 commit 7656635
Show file tree
Hide file tree
Showing 3 changed files with 45 additions and 7 deletions.
39 changes: 39 additions & 0 deletions src/attr/converters.py
Original file line number Diff line number Diff line change
Expand Up @@ -109,3 +109,42 @@ def default_if_none_converter(val):
return default

return default_if_none_converter


def to_bool(val):
"""
Convert "boolean" strings (e.g., from env. vars.) to real booleans.
Values mapping to :code:`True`:
- :code:`True`
- :code:`"true"` / :code:`"t"`
- :code:`"yes"` / :code:`"y"`
- :code:`"on"`
- :code:`"1"`
- :code:`1`
Values mapping to :code:`False`:
- :code:`False`
- :code:`"false"` / :code:`"f"`
- :code:`"no"` / :code:`"n"`
- :code:`"off"`
- :code:`"0"`
- :code:`0`
Raise :exc:`ValueError` for any other value.
"""
if isinstance(val, str):
val = val.lower()
truthy = {True, "true", "t", "yes", "y", "on", "1", 1}
falsy = {False, "false", "f", "no", "n", "off", "0", 0}
try:
if val in truthy:
return True
if val in falsy:
return False
except TypeError:
# Raised when "val" is not hashable (e.g., lists)
pass
raise ValueError(f"Cannot convert value to bool: {val}")
1 change: 1 addition & 0 deletions src/attr/converters.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,4 @@ def optional(converter: _ConverterType) -> _ConverterType: ...
def default_if_none(default: _T) -> _ConverterType: ...
@overload
def default_if_none(*, factory: Callable[[], _T]) -> _ConverterType: ...
def to_bool(val: str) -> bool: ...
12 changes: 5 additions & 7 deletions tests/test_converters.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,12 @@

from __future__ import absolute_import

from distutils.util import strtobool

import pytest

import attr

from attr import Factory, attrib
from attr.converters import default_if_none, optional, pipe
from attr.converters import default_if_none, optional, pipe, to_bool


class TestOptional(object):
Expand Down Expand Up @@ -106,15 +104,15 @@ def test_success(self):
"""
Succeeds if all wrapped converters succeed.
"""
c = pipe(str, strtobool, bool)
c = pipe(str, to_bool, bool)

assert True is c("True") is c(True)

def test_fail(self):
"""
Fails if any wrapped converter fails.
"""
c = pipe(str, strtobool)
c = pipe(str, to_bool)

# First wrapped converter fails:
with pytest.raises(ValueError):
Expand All @@ -131,8 +129,8 @@ def test_sugar(self):

@attr.s
class C(object):
a1 = attrib(default="True", converter=pipe(str, strtobool, bool))
a2 = attrib(default=True, converter=[str, strtobool, bool])
a1 = attrib(default="True", converter=pipe(str, to_bool, bool))
a2 = attrib(default=True, converter=[str, to_bool, bool])

c = C()
assert True is c.a1 is c.a2

0 comments on commit 7656635

Please sign in to comment.