Skip to content

Commit 9cf1e26

Browse files
author
Zo Bot
committed
decode bytes/bytearray inputs in to_bool
os.environb yields bytes on POSIX and the documented use case for to_bool is reading values out of environment variables. The truthy and falsy lookup tuples only contain str and int, so a bytes('true') input raised 'Cannot convert value to bool: b\'true\'' even though its ASCII-decoded value is the canonical truthy literal. Decode bytes and bytearray as ASCII before the lowercase + lookup step, matching the str path. Non-ASCII bytes raise ValueError ('Cannot convert value to bool: ...') the same way an unknown string does, since the decoded text would still not match any known literal. Unknown byte values raise the same ValueError.
1 parent 005e2fb commit 9cf1e26

2 files changed

Lines changed: 69 additions & 0 deletions

File tree

src/attr/converters.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,7 @@ def to_bool(val):
135135
- ``"on"``
136136
- ``"1"``
137137
- ``1``
138+
- bytes/bytearray whose ASCII-decoded value matches any of the strings above
138139
139140
Values mapping to `False`:
140141
@@ -144,12 +145,26 @@ def to_bool(val):
144145
- ``"off"``
145146
- ``"0"``
146147
- ``0``
148+
- bytes/bytearray whose ASCII-decoded value matches any of the strings above
147149
148150
Raises:
149151
ValueError: For any other value.
150152
153+
.. versionchanged:: 26.2
154+
bytes/bytearray inputs are decoded as ASCII before lookup, so values
155+
read from environment variables as bytes (e.g. on Windows or from
156+
``os.environb``) work without a separate ``.decode("ascii")`` step at
157+
the call site.
158+
151159
.. versionadded:: 21.3.0
152160
"""
161+
if isinstance(val, (bytes, bytearray)):
162+
try:
163+
val = val.decode("ascii")
164+
except UnicodeDecodeError as e:
165+
msg = f"Cannot convert value to bool: {val!r}"
166+
raise ValueError(msg) from e
167+
153168
if isinstance(val, str):
154169
val = val.lower()
155170

tests/test_converters.py

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -364,3 +364,57 @@ def test_falsy(self):
364364
assert not to_bool("f")
365365
assert not to_bool("no")
366366
assert not to_bool("off")
367+
368+
@pytest.mark.parametrize("value", [b"true", b"t", b"yes", b"y", b"on", b"1"])
369+
def test_truthy_bytes(self, value):
370+
"""
371+
Bytes values that decode to a truthy keyword match the str truthy path.
372+
"""
373+
assert to_bool(value) is True
374+
375+
@pytest.mark.parametrize(
376+
"value", [b"false", b"f", b"no", b"n", b"off", b"0"]
377+
)
378+
def test_falsy_bytes(self, value):
379+
"""
380+
Bytes values that decode to a falsy keyword match the str falsy path.
381+
"""
382+
assert to_bool(value) is False
383+
384+
@pytest.mark.parametrize("value", [b"TRUE", b"Yes", b"OFF", b"1"])
385+
def test_bytes_are_lowercased(self, value):
386+
"""
387+
Bytes inputs follow the same case-insensitive lookup as str inputs.
388+
"""
389+
assert to_bool(value) is bool(value.decode("ascii").lower() in
390+
{"true", "t", "yes", "y", "on", "1"})
391+
392+
def test_bytearray_truthy(self):
393+
"""
394+
bytearray is a bytes subclass and should be decoded the same way.
395+
"""
396+
assert to_bool(bytearray(b"true")) is True
397+
assert to_bool(bytearray(b"off")) is False
398+
399+
def test_bytes_non_ascii_raises(self):
400+
"""
401+
Bytes that are not valid ASCII raise ValueError with the same shape as
402+
other unconvertible values.
403+
"""
404+
with pytest.raises(ValueError, match="Cannot convert value to bool"):
405+
to_bool(b"\xfftrue")
406+
407+
def test_bytes_unknown_keyword_raises(self):
408+
"""
409+
Bytes that decode to a value not in either keyword set still raise.
410+
"""
411+
with pytest.raises(ValueError, match="Cannot convert value to bool"):
412+
to_bool(b"maybe")
413+
414+
def test_bytes_empty_raises(self):
415+
"""
416+
An empty bytes value is not a valid keyword, so it raises the same
417+
error as an empty string would.
418+
"""
419+
with pytest.raises(ValueError, match="Cannot convert value to bool"):
420+
to_bool(b"")

0 commit comments

Comments
 (0)