Skip to content

Commit d187c35

Browse files
fix(feature-flags): bare flags default to true, robust coercion, drop wrapper
Address code review feedback: - _coerce_flag_value: wrap coercion in try/except (ValueError, TypeError) and log a warning instead of crashing startup on malformed values. - _parse_cli_feature_flags: bare --feature-flag KEY (no '=') now defaults to 'true' so registered bool flags work as toggles. - Remove the get_cli_feature_flag_registry() wrapper; export and use CLI_FEATURE_FLAG_REGISTRY directly in main.py and tests. Add tests for coercion-failure fallback and bare-flag default behavior. Co-authored-by: Amp <amp@ampcode.com> Amp-Thread-ID: https://ampcode.com/threads/T-019deba2-bfe2-7118-913c-562beee48972
1 parent 393248c commit d187c35

3 files changed

Lines changed: 45 additions & 19 deletions

File tree

comfy_api/feature_flags.py

Lines changed: 24 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
allowing graceful protocol evolution while maintaining backward compatibility.
66
"""
77

8+
import logging
89
from typing import Any, TypedDict
910

1011
from comfy.cli_args import args
@@ -27,11 +28,6 @@ class FeatureFlagInfo(TypedDict):
2728
}
2829

2930

30-
def get_cli_feature_flag_registry() -> dict[str, FeatureFlagInfo]:
31-
"""Return the registry of known CLI-settable feature flags."""
32-
return {k: dict(v) for k, v in CLI_FEATURE_FLAG_REGISTRY.items()}
33-
34-
3531
_COERCE_FNS: dict[str, Any] = {
3632
"bool": lambda v: v.lower() == "true",
3733
"int": lambda v: int(v),
@@ -40,26 +36,41 @@ def get_cli_feature_flag_registry() -> dict[str, FeatureFlagInfo]:
4036

4137

4238
def _coerce_flag_value(key: str, raw_value: str) -> Any:
43-
"""Coerce a raw string value using the registry type, or keep as string."""
39+
"""Coerce a raw string value using the registry type, or keep as string.
40+
41+
Returns the raw string if the key is unregistered, the type is unknown,
42+
or coercion fails (with a warning logged in the failure case).
43+
"""
4444
info = CLI_FEATURE_FLAG_REGISTRY.get(key)
4545
if info is None:
4646
return raw_value
4747
coerce = _COERCE_FNS.get(info["type"])
4848
if coerce is None:
4949
return raw_value
50-
return coerce(raw_value)
50+
try:
51+
return coerce(raw_value)
52+
except (ValueError, TypeError):
53+
logging.warning(
54+
"Could not coerce --feature-flag %s=%r to %s; using raw string.",
55+
key, raw_value, info["type"],
56+
)
57+
return raw_value
5158

5259

5360
def _parse_cli_feature_flags() -> dict[str, Any]:
54-
"""Parse --feature-flag key=value pairs from CLI args into a dict."""
61+
"""Parse --feature-flag key=value pairs from CLI args into a dict.
62+
63+
Items without '=' default to the value 'true' (bare flag form).
64+
"""
5565
result: dict[str, Any] = {}
5666
for item in getattr(args, "feature_flag", []):
57-
if "=" not in item:
58-
continue
59-
key, _, raw_value = item.partition("=")
67+
key, sep, raw_value = item.partition("=")
6068
key = key.strip()
61-
if key:
62-
result[key] = _coerce_flag_value(key, raw_value.strip())
69+
if not key:
70+
continue
71+
if not sep:
72+
raw_value = "true"
73+
result[key] = _coerce_flag_value(key, raw_value.strip())
6374
return result
6475

6576

main.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,8 @@
55

66
if args.list_feature_flags:
77
import json
8-
from comfy_api.feature_flags import get_cli_feature_flag_registry
9-
print(json.dumps(get_cli_feature_flag_registry(), indent=2)) # noqa: T201
8+
from comfy_api.feature_flags import CLI_FEATURE_FLAG_REGISTRY
9+
print(json.dumps(CLI_FEATURE_FLAG_REGISTRY, indent=2)) # noqa: T201
1010
raise SystemExit(0)
1111

1212
import os

tests-unit/feature_flags_test.py

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
get_connection_feature,
55
supports_feature,
66
get_server_features,
7-
get_cli_feature_flag_registry,
7+
CLI_FEATURE_FLAG_REGISTRY,
88
SERVER_FEATURE_FLAGS,
99
_coerce_flag_value,
1010
_parse_cli_feature_flags,
@@ -116,6 +116,15 @@ def test_unregistered_key_stays_string(self):
116116
assert _coerce_flag_value("unknown_flag", "true") == "true"
117117
assert _coerce_flag_value("unknown_flag", "42") == "42"
118118

119+
def test_failed_coercion_falls_back_to_string(self, monkeypatch):
120+
"""Malformed values for typed flags must not crash; raw string is returned."""
121+
monkeypatch.setitem(
122+
CLI_FEATURE_FLAG_REGISTRY,
123+
"test_int_flag",
124+
{"type": "int", "default": 0, "description": "test"},
125+
)
126+
assert _coerce_flag_value("test_int_flag", "not_a_number") == "not_a_number"
127+
119128

120129
class TestParseCliFeatureFlags:
121130
"""Test suite for _parse_cli_feature_flags."""
@@ -125,8 +134,14 @@ def test_single_flag(self, monkeypatch):
125134
result = _parse_cli_feature_flags()
126135
assert result == {"show_signin_button": True}
127136

128-
def test_missing_equals_skipped(self, monkeypatch):
129-
monkeypatch.setattr("comfy_api.feature_flags.args", type("Args", (), {"feature_flag": ["noequals", "valid=1"]})())
137+
def test_missing_equals_defaults_to_true(self, monkeypatch):
138+
"""Bare flag without '=' is treated as the string 'true' (and coerced if registered)."""
139+
monkeypatch.setattr("comfy_api.feature_flags.args", type("Args", (), {"feature_flag": ["show_signin_button", "valid=1"]})())
140+
result = _parse_cli_feature_flags()
141+
assert result == {"show_signin_button": True, "valid": "1"}
142+
143+
def test_empty_key_skipped(self, monkeypatch):
144+
monkeypatch.setattr("comfy_api.feature_flags.args", type("Args", (), {"feature_flag": ["=value", "valid=1"]})())
130145
result = _parse_cli_feature_flags()
131146
assert result == {"valid": "1"}
132147

@@ -135,7 +150,7 @@ class TestCliFeatureFlagRegistry:
135150
"""Test suite for the CLI feature flag registry."""
136151

137152
def test_registry_entries_have_required_fields(self):
138-
for key, info in get_cli_feature_flag_registry().items():
153+
for key, info in CLI_FEATURE_FLAG_REGISTRY.items():
139154
assert "type" in info, f"{key} missing 'type'"
140155
assert "default" in info, f"{key} missing 'default'"
141156
assert "description" in info, f"{key} missing 'description'"

0 commit comments

Comments
 (0)