From 79feb93e1b4b80bfff08e374afac1608afadd234 Mon Sep 17 00:00:00 2001 From: eeshsaxena Date: Wed, 15 Jul 2026 00:33:36 +0530 Subject: [PATCH] fix: compare the whole value in env2bool env2bool used re.search, so it matched a truthy token anywhere inside the value. Any value merely containing "1" or "y" enabled the flag: DVC_EXP_AUTO_PUSH=deny -> True DVC_SQLALCHEMY_ECHO=10 -> True Compare the stripped, lowercased value against the truthy set instead. The accepted values are unchanged (1/y/yes/true, case-insensitive, surrounding whitespace tolerated); only values that happened to contain one of them are now correctly false. --- dvc/utils/__init__.py | 11 ++++++++--- tests/unit/utils/test_env2bool.py | 31 +++++++++++++++++++++++++++++++ 2 files changed, 39 insertions(+), 3 deletions(-) create mode 100644 tests/unit/utils/test_env2bool.py diff --git a/dvc/utils/__init__.py b/dvc/utils/__init__.py index ee0575212c..051f819ef3 100644 --- a/dvc/utils/__init__.py +++ b/dvc/utils/__init__.py @@ -237,14 +237,19 @@ def as_posix(path: str) -> str: return path.replace(ntpath.sep, posixpath.sep) +TRUTHY_VALUES = frozenset({"1", "y", "yes", "true"}) + + def env2bool(var, undefined=False): """ undefined: return value if env var is unset """ - var = os.getenv(var, None) - if var is None: + value = os.getenv(var, None) + if value is None: return undefined - return bool(re.search("1|y|yes|true", var, flags=re.IGNORECASE)) + # Compare the whole value: a substring search treats any value that merely + # contains "1" or "y" (e.g. "deny", "only", "10") as true. + return value.strip().lower() in TRUTHY_VALUES def resolve_output(inp: str, out: Optional[str], force=False) -> str: diff --git a/tests/unit/utils/test_env2bool.py b/tests/unit/utils/test_env2bool.py new file mode 100644 index 0000000000..3309edbe89 --- /dev/null +++ b/tests/unit/utils/test_env2bool.py @@ -0,0 +1,31 @@ +import pytest + +from dvc.utils import env2bool + +VAR = "DVC_TEST_ENV2BOOL" + + +def test_unset_returns_undefined(monkeypatch): + monkeypatch.delenv(VAR, raising=False) + assert env2bool(VAR) is False + assert env2bool(VAR, undefined=True) is True + + +@pytest.mark.parametrize("value", ["1", "y", "yes", "true", "TRUE", "Yes", " true "]) +def test_truthy_values(monkeypatch, value): + monkeypatch.setenv(VAR, value) + assert env2bool(VAR) is True + + +@pytest.mark.parametrize("value", ["0", "n", "no", "false", "off", ""]) +def test_falsy_values(monkeypatch, value): + monkeypatch.setenv(VAR, value) + assert env2bool(VAR) is False + + +@pytest.mark.parametrize("value", ["deny", "only", "anything", "my-value", "10", "1a"]) +def test_values_merely_containing_a_truthy_token_are_false(monkeypatch, value): + # The value is compared as a whole: a substring search would treat anything + # containing "1" or "y" as true, so "deny" and "10" would enable a flag. + monkeypatch.setenv(VAR, value) + assert env2bool(VAR) is False