Skip to content

Commit 56ef84b

Browse files
rustyconoverclaude
andcommitted
fix(arguments): satisfy strict mypy + ruff for union-tag decode
CI lint/type gates (not run locally before the prior push) flagged the union decode: drop the redundant UnionType cast, coerce type_code to int (the pyarrow stub types it as str though it is an int at runtime) so the type_codes lookup type-checks, and add docstrings to the new union tests. ruff, mypy (vgi/), and the union tests all clean locally. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent d0eb85f commit 56ef84b

2 files changed

Lines changed: 21 additions & 15 deletions

File tree

‎tests/test_union_argument.py‎

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,8 @@
1717
_SVC = pa.struct([pa.field("C", pa.list_(pa.float64())), pa.field("kernel", pa.list_(pa.string()))])
1818

1919

20-
def _union_scalar(tag: str, code: int, members: dict) -> pa.UnionScalar:
21-
"""Build a one-element sparse-union scalar with named members, active = ``tag``."""
20+
def _union_scalar(code: int, members: dict) -> pa.UnionScalar:
21+
"""Build a one-element sparse-union scalar with named members, active = ``code``."""
2222
arr = pa.UnionArray.from_sparse(
2323
pa.array([code], type=pa.int8()),
2424
[
@@ -32,23 +32,24 @@ def _union_scalar(tag: str, code: int, members: dict) -> pa.UnionScalar:
3232

3333

3434
def test_union_arg_preserves_tag() -> None:
35-
scalar = _union_scalar(
36-
"random_forest_classifier", 0, {"random_forest_classifier": {"n_estimators": [100, 300], "max_depth": [3, 5]}}
37-
)
35+
"""A union argument decodes to a TaggedUnion carrying the active member name."""
36+
scalar = _union_scalar(0, {"random_forest_classifier": {"n_estimators": [100, 300], "max_depth": [3, 5]}})
3837
got = Arguments(named={"config": scalar}).get("config")
3938
assert isinstance(got, TaggedUnion)
4039
assert got.tag == "random_forest_classifier"
4140
assert got.value == {"n_estimators": [100, 300], "max_depth": [3, 5]}
4241

4342

4443
def test_union_arg_other_member() -> None:
45-
scalar = _union_scalar("svc", 1, {"svc": {"C": [1.0, 10.0], "kernel": ["rbf", "linear"]}})
44+
"""The tag reflects whichever union member is set."""
45+
scalar = _union_scalar(1, {"svc": {"C": [1.0, 10.0], "kernel": ["rbf", "linear"]}})
4646
got = Arguments(named={"config": scalar}).get("config")
4747
assert got.tag == "svc"
4848
assert got.value == {"C": [1.0, 10.0], "kernel": ["rbf", "linear"]}
4949

5050

5151
def test_non_union_args_unchanged() -> None:
52+
"""Non-union arguments still decode via plain as_py()."""
5253
args = Arguments(named={"n": pa.scalar(5), "s": pa.scalar("hi")}, positional=(pa.scalar(1.5),))
5354
assert args.get("n") == 5
5455
assert args.get("s") == "hi"

‎vgi/arguments.py‎

Lines changed: 14 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -194,23 +194,28 @@ class TaggedUnion:
194194
value: Any
195195

196196

197-
def _scalar_to_py(scalar: "Scalar") -> Any:
197+
def _scalar_to_py(scalar: "Scalar[Any]") -> Any:
198198
"""Convert an argument scalar to a Python value, preserving union tags.
199199
200200
Identical to ``scalar.as_py()`` for every type except unions: a
201201
``UnionScalar`` is decoded to a [`TaggedUnion`][] so the member
202202
discriminator (which ``as_py()`` discards) is retained.
203+
204+
Args:
205+
scalar: The argument scalar to convert.
206+
207+
Returns:
208+
``scalar.as_py()`` for non-union scalars; a [`TaggedUnion`][] for unions.
209+
203210
"""
204211
if isinstance(scalar, pa.UnionScalar):
212+
# Map the active ``type_code`` to its member field name via the union
213+
# type's parallel ``type_codes`` / ``field()``. (``type_code`` is coerced
214+
# to int — it is an integer at runtime regardless of the stub's typing.)
205215
union_type = scalar.type
206-
tag = next(
207-
(
208-
union_type.field(i).name
209-
for i in range(union_type.num_fields)
210-
if union_type.type_codes[i] == scalar.type_code
211-
),
212-
None,
213-
)
216+
type_codes = list(union_type.type_codes)
217+
code = int(scalar.type_code)
218+
tag = union_type.field(type_codes.index(code)).name if code in type_codes else None
214219
inner = scalar.value
215220
return TaggedUnion(tag=tag, value=inner.as_py() if inner is not None else None)
216221
return scalar.as_py()

0 commit comments

Comments
 (0)