From e75d50f041fa965d6052c60d1fcb6f845268ae7a Mon Sep 17 00:00:00 2001 From: gaoflow Date: Tue, 9 Jun 2026 17:28:30 +0200 Subject: [PATCH] Serialize datetime-like values in _orjson_default (fixes #458) A figure containing a pandas Timestamp (e.g. a marker x-position) raised `TypeError: Type is not JSON serializable: Timestamp` from write_image / calc_fig. The orjson fallback `_orjson_default` only handled `Decimal` and objects exposing `.tolist()` (NumPy); a Timestamp has neither, so it fell through to `raise TypeError`. Serialize datetime-like objects (anything with `.isoformat()`, e.g. pandas `Timestamp`, `datetime`, `date`) to ISO strings, matching how Plotly's own JSON encoder handles them. --- CHANGELOG.md | 3 +++ src/py/kaleido/_kaleido_tab/_tab.py | 2 ++ src/py/tests/test_orjson_encoder.py | 31 +++++++++++++++++++++++++++++ 3 files changed, 36 insertions(+) create mode 100644 src/py/tests/test_orjson_encoder.py diff --git a/CHANGELOG.md b/CHANGELOG.md index b96df3dc..f92e7c8a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,8 @@ ## Unreleased +### Fixed +- Fixed `TypeError: Type is not JSON serializable: Timestamp` when a figure contains datetime-like values such as a `pandas` `Timestamp`; these now serialize to ISO strings [[#458](https://github.com/plotly/Kaleido/issues/458)] + ## v1.3.0 ### Added diff --git a/src/py/kaleido/_kaleido_tab/_tab.py b/src/py/kaleido/_kaleido_tab/_tab.py index 3837d76a..800676ea 100644 --- a/src/py/kaleido/_kaleido_tab/_tab.py +++ b/src/py/kaleido/_kaleido_tab/_tab.py @@ -30,6 +30,8 @@ def _orjson_default(obj): """Fallback for types orjson can't handle natively (e.g. NumPy string arrays).""" if isinstance(obj, Decimal): return float(obj) + if hasattr(obj, "isoformat"): # datetime-like, e.g. pandas Timestamp (#458) + return obj.isoformat() if hasattr(obj, "tolist"): return obj.tolist() raise TypeError(f"Type is not JSON serializable: {type(obj).__name__}") diff --git a/src/py/tests/test_orjson_encoder.py b/src/py/tests/test_orjson_encoder.py new file mode 100644 index 00000000..fa3ae4d5 --- /dev/null +++ b/src/py/tests/test_orjson_encoder.py @@ -0,0 +1,31 @@ +import datetime +from decimal import Decimal + +import numpy as np +import orjson +import pandas as pd + +from kaleido._kaleido_tab._tab import _orjson_default + + +def test_orjson_default_handles_datetime_like(): + # A pandas Timestamp has no ``.tolist()`` and used to raise TypeError (#458); + # _orjson_default should fall back to an ISO string, like Plotly's encoder. + ts = pd.Timestamp("2026-06-03 12:00:00") + assert _orjson_default(ts) == ts.isoformat() + + a_date = datetime.date(2026, 1, 2) + assert _orjson_default(a_date) == a_date.isoformat() + + # A figure spec carrying a Timestamp now round-trips through orjson. + spec = {"x": [ts]} + dumped = orjson.dumps( + spec, default=_orjson_default, option=orjson.OPT_SERIALIZE_NUMPY + ) + assert ts.isoformat().encode() in dumped + + # Existing fallbacks are unaffected. + decimal_value = Decimal("1.5") + assert _orjson_default(decimal_value) == float(decimal_value) + array_values = [1, 2, 3] + assert _orjson_default(np.array(array_values)) == array_values