Skip to content

Commit ecff4c0

Browse files
authored
Replace to_bool calls with boolean function (#4542)
This PR replaces all calls to the to_bool function with the existing boolean function for consistency. Changes: - Replace to_bool function calls with boolean function calls in config.py and ansi_output.py - Add optional default parameter to boolean function to return fallback value for invalid inputs - Apply default parameter to environment variable parsing to prevent TypeError exceptions - Move boolean function tests from test_ansi_output.py to test_util.py for proper organization - Add comprehensive test coverage for the new default parameter functionality - Update type annotations and add proper docstring documentation The boolean function now accepts a default parameter that provides graceful fallback for invalid environment variable values while maintaining strict behavior when no default is specified.
1 parent 5959d0f commit ecff4c0

File tree

5 files changed

+94
-43
lines changed

5 files changed

+94
-43
lines changed

src/molecule/ansi_output.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@
3030

3131
from molecule.constants import DEFAULT_BORDER_WIDTH, MARKUP_MAP, SCENARIO_RECAP_STATE_ORDER
3232
from molecule.constants import ANSICodes as A
33-
from molecule.util import to_bool
33+
from molecule.util import boolean
3434

3535

3636
if TYPE_CHECKING:
@@ -53,7 +53,7 @@ def should_do_markup() -> bool:
5353
for v in ["PY_COLORS", "CLICOLOR", "FORCE_COLOR", "ANSIBLE_FORCE_COLOR"]:
5454
value = os.environ.get(v, None)
5555
if value is not None:
56-
py_colors = to_bool(value)
56+
py_colors = boolean(value, default=False)
5757
break
5858

5959
# If deliberately disabled colors
@@ -62,7 +62,7 @@ def should_do_markup() -> bool:
6262

6363
# User configuration requested colors
6464
if py_colors is not None:
65-
return to_bool(py_colors)
65+
return boolean(py_colors)
6666

6767
term = os.environ.get("TERM", "")
6868
if "xterm" in term:

src/molecule/config.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@
4040
from molecule.dependency import ansible_galaxy, shell
4141
from molecule.model import schema_v3
4242
from molecule.provisioner import ansible
43-
from molecule.util import boolean, sysexit_with_message, to_bool
43+
from molecule.util import boolean, sysexit_with_message
4444

4545

4646
if TYPE_CHECKING:
@@ -61,8 +61,8 @@
6161
from molecule.verifier.base import Verifier
6262

6363

64-
MOLECULE_PARALLEL: bool = boolean(os.environ.get("MOLECULE_PARALLEL", ""))
65-
MOLECULE_DEBUG: bool = boolean(os.environ.get("MOLECULE_DEBUG", "False"))
64+
MOLECULE_PARALLEL: bool = boolean(os.environ.get("MOLECULE_PARALLEL", ""), default=False)
65+
MOLECULE_DEBUG: bool = boolean(os.environ.get("MOLECULE_DEBUG", "False"), default=False)
6666
MOLECULE_VERBOSITY: int = int(os.environ.get("MOLECULE_VERBOSITY", "0"))
6767
MOLECULE_DIRECTORY = "molecule"
6868
MOLECULE_FILE = "molecule.yml"
@@ -187,7 +187,7 @@ def _apply_env_overrides(self) -> None:
187187

188188
try:
189189
converted_value = (
190-
to_bool(env_value) if expected_type is bool else expected_type(env_value)
190+
boolean(env_value) if expected_type is bool else expected_type(env_value)
191191
)
192192
self.command_args[config_attr] = converted_value
193193
except (ValueError, TypeError):

src/molecule/util.py

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@
4949
if TYPE_CHECKING:
5050
from collections.abc import Generator, Iterable, MutableMapping, Sequence
5151
from io import TextIOWrapper
52-
from typing import Any, AnyStr, NoReturn, TypeVar
52+
from typing import Any, NoReturn, TypeVar
5353
from warnings import WarningMessage
5454

5555
from molecule.types import CollectionData, CommandArgs, ConfigData, Options, PlatformData
@@ -495,18 +495,18 @@ def lookup_config_file(filename: str) -> str | None:
495495
return None
496496

497497

498-
def boolean(value: bool | AnyStr, *, strict: bool = True) -> bool: # noqa: FBT001
498+
def boolean(value: object, *, default: bool | None = None) -> bool:
499499
"""Evaluate any object as boolean matching ansible behavior.
500500
501501
Args:
502502
value: The value to evaluate as a boolean.
503-
strict: If True, invalid booleans will raises TypeError instead of returning False.
503+
default: If provided, return this value for invalid inputs instead of raising TypeError.
504504
505505
Returns:
506-
The boolean value of value.
506+
The boolean value of value, or default if value is invalid and default is provided.
507507
508508
Raises:
509-
TypeError: If value does not resolve to a valid boolean and strict is True.
509+
TypeError: If value does not resolve to a valid boolean and no default is provided.
510510
"""
511511
# Based on https://github.com/ansible/ansible/blob/devel/lib/ansible/module_utils/parsing/convert_bool.py
512512

@@ -525,9 +525,13 @@ def boolean(value: bool | AnyStr, *, strict: bool = True) -> bool: # noqa: FBT0
525525

526526
if normalized_value in BOOLEANS_TRUE:
527527
return True
528-
if normalized_value in BOOLEANS_FALSE or not strict:
528+
if normalized_value in BOOLEANS_FALSE:
529529
return False
530530

531+
# If we have a default, return it for invalid values
532+
if default is not None:
533+
return default
534+
531535
raise TypeError( # noqa: TRY003
532536
f"The value '{value!s}' is not a valid boolean. Valid booleans include: {', '.join(repr(i) for i in BOOLEANS)!s}", # noqa: EM102
533537
)

tests/unit/test_ansi_output.py

Lines changed: 0 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -6,35 +6,6 @@
66

77
from molecule.ansi_output import AnsiOutput, should_do_markup
88
from molecule.constants import ANSICodes as A
9-
from molecule.util import to_bool
10-
11-
12-
@pytest.mark.parametrize(
13-
("input_value", "expected"),
14-
(
15-
(None, False),
16-
(True, True),
17-
(False, False),
18-
("yes", True),
19-
("YES", True),
20-
("on", True),
21-
("ON", True),
22-
("1", True),
23-
("true", True),
24-
("TRUE", True),
25-
("no", False),
26-
("off", False),
27-
("0", False),
28-
("false", False),
29-
("random", False),
30-
(1, True),
31-
(0, False),
32-
(42, False),
33-
),
34-
)
35-
def test_to_bool(input_value: object, expected: bool) -> None: # noqa: FBT001
36-
"""Test to_bool function with various inputs."""
37-
assert to_bool(input_value) is expected
389

3910

4011
def test_should_do_markup_basic() -> None:

tests/unit/test_util.py

Lines changed: 77 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# Copyright (c) 2015-2018 Cisco Systems, Inc. # noqa: D100
1+
# Copyright (c) 2015-2018 Cisco Systems, Inc.
22
#
33
# Permission is hereby granted, free of charge, to any person obtaining a copy
44
# of this software and associated documentation files (the "Software"), to
@@ -17,6 +17,10 @@
1717
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
1818
# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
1919
# DEALINGS IN THE SOFTWARE.
20+
"""Test the util module."""
21+
22+
# pylint: disable=too-many-lines
23+
2024
from __future__ import annotations
2125

2226
import binascii
@@ -938,3 +942,75 @@ def test_lookup_config_file_collection_no_extensions_dir(
938942

939943
# Should fall back to home directory since collection extensions dir doesn't exist
940944
assert result == str(home_config)
945+
946+
947+
@pytest.mark.parametrize(
948+
("input_value", "expected"),
949+
(
950+
(True, True),
951+
(False, False),
952+
("yes", True),
953+
("YES", True),
954+
("on", True),
955+
("ON", True),
956+
("1", True),
957+
("true", True),
958+
("TRUE", True),
959+
("no", False),
960+
("off", False),
961+
("0", False),
962+
("false", False),
963+
(1, True),
964+
(0, False),
965+
("", False),
966+
),
967+
)
968+
def test_boolean_valid_inputs(input_value: object, expected: bool) -> None: # noqa: FBT001
969+
"""Test boolean function with valid inputs.
970+
971+
Args:
972+
input_value: The input value to test.
973+
expected: The expected boolean result.
974+
"""
975+
assert util.boolean(input_value) is expected
976+
977+
978+
@pytest.mark.parametrize(
979+
"input_value",
980+
(
981+
None,
982+
"random",
983+
42,
984+
"invalid",
985+
),
986+
)
987+
def test_boolean_invalid_values(input_value: object) -> None:
988+
"""Test boolean function raises TypeError for invalid inputs.
989+
990+
Args:
991+
input_value: The invalid input value to test.
992+
"""
993+
with pytest.raises(TypeError):
994+
util.boolean(input_value)
995+
996+
997+
@pytest.mark.parametrize(
998+
("input_value", "default_value", "expected"),
999+
(
1000+
(None, False, False),
1001+
("random", True, True),
1002+
(42, False, False),
1003+
("invalid", True, True),
1004+
("yes", False, True), # Valid input should ignore default
1005+
("no", True, False), # Valid input should ignore default
1006+
),
1007+
)
1008+
def test_boolean_with_default(input_value: object, default_value: bool, expected: bool) -> None: # noqa: FBT001
1009+
"""Test boolean function with default parameter.
1010+
1011+
Args:
1012+
input_value: The input value to test.
1013+
default_value: The default value to use for invalid inputs.
1014+
expected: The expected boolean result.
1015+
"""
1016+
assert util.boolean(input_value, default=default_value) is expected

0 commit comments

Comments
 (0)