Skip to content

Commit 6ce5ed0

Browse files
mstoclaude
andcommitted
fix: Use JSON mode for model serialization in MetricWriter
Use `model_dump(mode="json")` in `MetricWriter.write()` so that pydantic serializers run fully. Handle both `str` and `StrEnum` keys in `_pivot_counter_values` to support the already-converted keys that JSON mode produces. Co-Authored-By: Rahul Kaushal <kaushalrahul15@gmail.com> Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 41f2ec0 commit 6ce5ed0

File tree

4 files changed

+50
-2
lines changed

4 files changed

+50
-2
lines changed

fgmetric/collections/_counter_pivot_table.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -181,6 +181,7 @@ def _pivot_counter_values(
181181
# Replace the counter field with keys for each of its enum's members
182182
counts = data.pop(self._counter_fieldname)
183183
for key, count in counts.items():
184-
data[key.value] = count
184+
column_name = key if isinstance(key, str) else key.value
185+
data[column_name] = count
185186

186187
return data

fgmetric/metric_writer.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -102,7 +102,7 @@ def write(self, metric: T) -> None:
102102
TypeError: If the provided `metric` is not an instance of the Metric class used to
103103
parametrize the writer.
104104
"""
105-
self._writer.writerow(metric.model_dump())
105+
self._writer.writerow(metric.model_dump(mode="json"))
106106

107107
def writeall(self, metrics: Iterable[T]) -> None:
108108
"""

tests/test_collections.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -204,6 +204,24 @@ class FakeMetric(Metric):
204204
next(f)
205205

206206

207+
def test_counter_pivot_table_model_dump_json_mode() -> None:
208+
"""Test that model_dump(mode='json') works with Counter pivot tables."""
209+
210+
@unique
211+
class FakeEnum(StrEnum):
212+
FOO = "foo"
213+
BAR = "bar"
214+
215+
class FakeMetric(Metric):
216+
name: str
217+
counts: Counter[FakeEnum]
218+
219+
metric = FakeMetric(name="test", counts=Counter({FakeEnum.FOO: 1, FakeEnum.BAR: 2}))
220+
result = metric.model_dump(mode="json")
221+
222+
assert result == {"name": "test", "foo": 1, "bar": 2}
223+
224+
207225
def test_counter_pivot_table_missing_enum_members_default_to_zero(tmp_path: Path) -> None:
208226
"""Test that missing enum members in input default to 0."""
209227

tests/test_metric_writer.py

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
from collections import Counter
2+
from enum import StrEnum
3+
from enum import unique
14
from pathlib import Path
25
from typing import assert_type
36

@@ -30,3 +33,29 @@ def test_writer(tmp_path: Path) -> None:
3033
assert next(f) == "def\t2\n"
3134
with pytest.raises(StopIteration):
3235
next(f)
36+
37+
38+
def test_writer_with_counter_metric(tmp_path: Path) -> None:
39+
"""Test we can write a Counter metric through MetricWriter."""
40+
41+
@unique
42+
class FakeEnum(StrEnum):
43+
FOO = "foo"
44+
BAR = "bar"
45+
46+
class CounterMetric(Metric):
47+
name: str
48+
counts: Counter[FakeEnum]
49+
50+
fpath = tmp_path / "test.txt"
51+
52+
with MetricWriter(CounterMetric, fpath) as writer:
53+
writer.write(
54+
CounterMetric(name="test", counts=Counter({FakeEnum.FOO: 3, FakeEnum.BAR: 4}))
55+
)
56+
57+
with fpath.open("r") as f:
58+
assert next(f) == "name\tfoo\tbar\n"
59+
assert next(f) == "test\t3\t4\n"
60+
with pytest.raises(StopIteration):
61+
next(f)

0 commit comments

Comments
 (0)