Skip to content

Commit 178e274

Browse files
Added more fixes for parsing discriminated unions (#1779)
Co-authored-by: openhands <openhands@all-hands.dev>
1 parent cf43183 commit 178e274

File tree

2 files changed

+81
-1
lines changed

2 files changed

+81
-1
lines changed

openhands-agent-server/openhands/agent_server/env_parser.py

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -278,8 +278,10 @@ class DiscriminatedUnionEnvParser(EnvParser):
278278

279279
def from_env(self, key: str) -> JsonType:
280280
kind = os.environ.get(f"{key}_KIND", MISSING)
281+
kind_missing = False
281282
if kind is MISSING:
282-
# If there is exactly one kind, use it directly
283+
kind_missing = True
284+
# If there are other fields and there is exactly one kind, use it directly
283285
if len(self.parsers) == 1:
284286
kind = next(iter(self.parsers.keys()))
285287
else:
@@ -294,6 +296,15 @@ def from_env(self, key: str) -> JsonType:
294296
# Intentionally raise KeyError for invalid KIND - typos should fail early
295297
parser = self.parsers[kind]
296298
parser_result = parser.from_env(key)
299+
300+
# A kind was defined without other fields
301+
if parser_result is MISSING:
302+
# If the kind was not defined, the entry is MISSING
303+
if kind_missing:
304+
return MISSING
305+
# Only a kind was defined
306+
parser_result = {}
307+
297308
# Type narrowing: discriminated union parsers always return dicts
298309
parser_result = cast(dict, parser_result)
299310
parser_result["kind"] = kind

tests/agent_server/test_env_parser.py

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1309,3 +1309,72 @@ def test_discriminated_union_full_class_name_invalid_class(clean_env):
13091309

13101310
with pytest.raises(AttributeError):
13111311
parser.from_env("TEST")
1312+
1313+
1314+
def test_discriminated_union_kind_only_no_other_variables(clean_env):
1315+
"""Test that DiscriminatedUnionEnvParser handles types that define only a kind
1316+
without any other variables."""
1317+
# Create a parser with no additional fields (empty parser that returns MISSING)
1318+
empty_parser = ModelEnvParser(parsers={}, descriptions={})
1319+
parser = DiscriminatedUnionEnvParser(parsers={"EmptyKind": empty_parser})
1320+
1321+
# Set KIND but no other environment variables
1322+
os.environ["TEST_KIND"] = "EmptyKind"
1323+
1324+
# Should return just the kind, not MISSING
1325+
result = parser.from_env("TEST")
1326+
assert result == {"kind": "EmptyKind"}
1327+
1328+
1329+
def test_discriminated_union_kind_only_multiple_kinds(clean_env):
1330+
"""Test that when KIND is set to a type with no fields among multiple kinds,
1331+
it still works correctly."""
1332+
# Create parsers - one with fields, one without
1333+
empty_parser = ModelEnvParser(parsers={}, descriptions={})
1334+
dog_parser = ModelEnvParser(
1335+
parsers={"name": StrEnvParser(), "barking": BoolEnvParser()},
1336+
descriptions={},
1337+
)
1338+
parser = DiscriminatedUnionEnvParser(
1339+
parsers={"EmptyKind": empty_parser, "Dog": dog_parser}
1340+
)
1341+
1342+
# Set KIND to the empty type
1343+
os.environ["TEST_KIND"] = "EmptyKind"
1344+
1345+
# Should return just the kind
1346+
result = parser.from_env("TEST")
1347+
assert result == {"kind": "EmptyKind"}
1348+
1349+
1350+
def test_discriminated_union_no_kind_no_variables_returns_missing(clean_env):
1351+
"""Test that when KIND is not set and parser returns MISSING,
1352+
the result is MISSING (not an empty dict with no kind)."""
1353+
# Create a parser with no additional fields
1354+
empty_parser = ModelEnvParser(parsers={}, descriptions={})
1355+
non_empty_parser = ModelEnvParser(
1356+
parsers={"name": StrEnvParser()},
1357+
descriptions={},
1358+
)
1359+
parser = DiscriminatedUnionEnvParser(
1360+
parsers={"EmptyKind": empty_parser, "NonEmpty": non_empty_parser}
1361+
)
1362+
1363+
# Don't set KIND or any other variables
1364+
# Should return MISSING because there are multiple kinds and no KIND is set
1365+
result = parser.from_env("TEST")
1366+
assert result is MISSING
1367+
1368+
1369+
def test_discriminated_union_single_empty_kind_no_variables(clean_env):
1370+
"""Test that when there's exactly one empty kind and no env vars are set,
1371+
the result is MISSING (the entry is not configured)."""
1372+
# Create a single empty parser
1373+
empty_parser = ModelEnvParser(parsers={}, descriptions={})
1374+
parser = DiscriminatedUnionEnvParser(parsers={"EmptyKind": empty_parser})
1375+
1376+
# Don't set any environment variables (not even KIND)
1377+
# With a single kind, it should try the parser but still return MISSING
1378+
# because there's no indication that this entry is configured
1379+
result = parser.from_env("TEST")
1380+
assert result is MISSING

0 commit comments

Comments
 (0)