Skip to content

Commit e38f7ce

Browse files
author
Izaak Gough
committed
fix: stringify tuple dict keys in exception args
1 parent b2c4a15 commit e38f7ce

2 files changed

Lines changed: 32 additions & 1 deletion

File tree

src/firebase_functions/logger.py

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -148,14 +148,29 @@ def _coerce_json_safe(obj: _typing.Any):
148148
if isinstance(obj, str | int | float | bool | type(None)):
149149
return obj
150150
if isinstance(obj, dict):
151-
return {_coerce_json_safe(key): _coerce_json_safe(value) for key, value in obj.items()}
151+
return {
152+
_coerce_json_safe_dict_key(key): _coerce_json_safe(value) for key, value in obj.items()
153+
}
152154
if isinstance(obj, list):
153155
return [_coerce_json_safe(item) for item in obj]
154156
if isinstance(obj, tuple):
155157
return tuple(_coerce_json_safe(item) for item in obj)
156158
return _safe_repr(obj)
157159

158160

161+
def _coerce_json_safe_dict_key(obj: _typing.Any):
162+
"""
163+
Converts dictionary keys into values accepted by JSON object encoding.
164+
"""
165+
166+
if isinstance(obj, str | int | float | bool | type(None)):
167+
return obj
168+
coerced = _coerce_json_safe(obj)
169+
if isinstance(coerced, str | int | float | bool | type(None)):
170+
return coerced
171+
return _safe_repr(coerced)
172+
173+
159174
def _safe_repr(obj: _typing.Any) -> str:
160175
"""
161176
Returns a repr without propagating repr errors.

tests/test_logger.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -179,6 +179,22 @@ def test_error_should_accept_exception_with_non_json_serializable_dict_key(
179179
assert log_output["error"]["type"] == "ValueError"
180180
assert log_output["error"]["args"] == [{repr(next(iter(payload.keys()))): "value"}]
181181

182+
def test_error_should_accept_exception_with_tuple_dict_key(
183+
self, capsys: pytest.CaptureFixture[str]
184+
):
185+
payload = {(1, "two"): "value"}
186+
exception = ValueError(payload)
187+
188+
logger.error("failed", error=exception)
189+
190+
raw_log_output = capsys.readouterr().err
191+
log_output = json.loads(raw_log_output)
192+
193+
assert log_output["severity"] == "ERROR"
194+
assert log_output["message"] == "failed"
195+
assert log_output["error"]["type"] == "ValueError"
196+
assert log_output["error"]["args"] == [{"(1, 'two')": "value"}]
197+
182198
def test_log_should_have_message(self, capsys: pytest.CaptureFixture[str]):
183199
logger.log("bar")
184200
raw_log_output = capsys.readouterr().out

0 commit comments

Comments
 (0)