Skip to content

Commit ef47f8b

Browse files
Reject unknown trust_server_heartbeat URL tokens loudly
The inline lambda silently coerced any non-truthy string to False, so a typo like ?trust_server_heartbeat=flase would disable the opt-in without error. Factor a strict _parse_url_bool helper that raises ArgumentError on anything outside the conventional bool token set, matching the loud-fail precedent established for the range validators. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent ee91768 commit ef47f8b

2 files changed

Lines changed: 41 additions & 2 deletions

File tree

src/sqlalchemydqlite/base.py

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,27 @@
1818
import dqlitedbapi.exceptions as _dbapi_exc
1919
from dqlitewire import LEADER_ERROR_CODES as _LEADER_CHANGE_CODES
2020

21+
_TRUE_TOKENS = frozenset({"1", "true", "yes", "on"})
22+
_FALSE_TOKENS = frozenset({"0", "false", "no", "off"})
23+
24+
25+
def _parse_url_bool(key: str, raw: str) -> bool:
26+
"""Strict bool parser for URL query parameters.
27+
28+
Accepts the conventional truthy/falsy token sets and raises
29+
``ArgumentError`` on anything else so a typo like ``?flag=flase``
30+
surfaces instead of silently coercing to False.
31+
"""
32+
token = raw.strip().lower()
33+
if token in _TRUE_TOKENS:
34+
return True
35+
if token in _FALSE_TOKENS:
36+
return False
37+
raise ArgumentError(
38+
f"Invalid bool value for URL parameter {key!r}: {raw!r} "
39+
f"(accepted: 1/0, true/false, yes/no, on/off)"
40+
)
41+
2142

2243
class _DqliteDateTime(sqltypes.DateTime):
2344
"""Passthrough DateTime — ``dqlitedbapi`` already returns ``datetime.datetime``
@@ -161,13 +182,15 @@ def import_dbapi(cls) -> types.ModuleType:
161182
# values (zero, negative, NaN, inf). The predicate may be ``None`` for
162183
# bool knobs that don't admit a range check.
163184
# ``trust_server_heartbeat`` uses a URL-friendly bool parser because
164-
# bool("False") evaluates truthy (non-empty string).
185+
# bool("False") evaluates truthy (non-empty string). Unknown tokens
186+
# raise ``ArgumentError`` to prevent a typo from silently disabling
187+
# the opt-in.
165188
_URL_QUERY_ALLOWED: dict[str, tuple[Callable[[str], Any], Callable[[Any], bool] | None]] = {
166189
"timeout": (float, lambda v: math.isfinite(v) and v > 0),
167190
"max_total_rows": (int, lambda v: v > 0),
168191
"max_continuation_frames": (int, lambda v: v > 0),
169192
"trust_server_heartbeat": (
170-
lambda s: s.strip().lower() in ("1", "true", "yes", "on"),
193+
lambda s: _parse_url_bool("trust_server_heartbeat", s),
171194
None,
172195
),
173196
}

tests/test_dialect_dialect_config.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,22 @@ def test_trust_server_heartbeat_parses_boolean(self, raw: str, expected: bool) -
126126
_, kwargs = dialect.create_connect_args(url)
127127
assert kwargs["trust_server_heartbeat"] is expected
128128

129+
@pytest.mark.parametrize(
130+
"raw",
131+
["enabled", "flase", "yse", "2", "maybe"],
132+
)
133+
def test_trust_server_heartbeat_rejects_unknown_tokens(self, raw: str) -> None:
134+
"""Unknown tokens must raise rather than silently coerce to False.
135+
136+
A typo in the URL would previously mean the operator thinks they
137+
opted into the flag but actually got the default — a surprising
138+
and hard-to-diagnose misconfiguration.
139+
"""
140+
dialect = DqliteDialect()
141+
url = make_url(f"dqlite://host:19001/db?trust_server_heartbeat={raw}")
142+
with pytest.raises(ArgumentError, match="Invalid bool value"):
143+
dialect.create_connect_args(url)
144+
129145

130146
class TestURLQueryRangeValidation:
131147
@pytest.mark.parametrize(

0 commit comments

Comments
 (0)