diff --git a/.changeset/code-interpreter-python-extra-mime.md b/.changeset/code-interpreter-python-extra-mime.md new file mode 100644 index 0000000000..8c7f2c7b79 --- /dev/null +++ b/.changeset/code-interpreter-python-extra-mime.md @@ -0,0 +1,5 @@ +--- +'@e2b/code-interpreter-python': patch +--- + +fix(code-interpreter): collect non-standard MIME types into `Result.extra` in the Python SDK — `Result.__init__` declared an `extra` field for data "not part of the standard types", but unknown keys from the kernel message were dropped via `**kwargs` instead of collected, so non-standard representations (e.g. `application/vnd.plotly.v1+json`, bokeh, vega-lite) were silently lost. They are now swept into `extra`, listed by `formats()`, and preserved by `Execution.to_json()`, matching the JS SDK's `Result` constructor. `serialize_results` reads extra keys from `Result.extra` rather than treating them as attributes. diff --git a/packages/code-interpreter-python/e2b_code_interpreter/models.py b/packages/code-interpreter-python/e2b_code_interpreter/models.py index bb0a7751ed..3ff555b83f 100644 --- a/packages/code-interpreter-python/e2b_code_interpreter/models.py +++ b/packages/code-interpreter-python/e2b_code_interpreter/models.py @@ -167,7 +167,14 @@ def __init__( f"Error deserializing chart, check if you are using the latest version of the library: {e}" ) self.is_main_result = is_main_result - self.extra = extra + # Collect non-standard MIME types (e.g. application/vnd.plotly.v1+json) + # into `extra`, matching the JS SDK's Result constructor, which sweeps + # every key outside its known set into `extra`. Without this the + # kernel's non-standard representations are silently dropped. + if kwargs: + self.extra = {**(extra or {}), **kwargs} + else: + self.extra = extra def formats(self) -> Iterable[str]: """ @@ -337,6 +344,8 @@ def serialize_results(results: List[Result]) -> List[Dict[str, str]]: chart = result.chart if chart is not None: serialized_dict[key] = chart.to_dict() + elif result.extra is not None and key in result.extra: + serialized_dict[key] = result.extra[key] else: serialized_dict[key] = result[key] diff --git a/packages/code-interpreter-python/tests/test_result_extra.py b/packages/code-interpreter-python/tests/test_result_extra.py new file mode 100644 index 0000000000..aca8f39ca3 --- /dev/null +++ b/packages/code-interpreter-python/tests/test_result_extra.py @@ -0,0 +1,74 @@ +import json + +from e2b_code_interpreter.models import Execution, Result, parse_output + + +def _result_from(message: dict) -> Result: + execution = Execution() + parse_output( + execution, json.dumps({"type": "result", "is_main_result": True, **message}) + ) + return execution.results[0] + + +def test_collects_non_standard_mime_types_into_extra(): + # The IPython kernel sends non-standard MIME types (e.g. plotly/bokeh) that + # aren't among the known representations. They should land in `extra`, like + # the JS SDK's Result constructor does — not be silently dropped. + result = _result_from( + {"text": "hello", "application/vnd.plotly.v1+json": {"data": [{"x": [1, 2]}]}} + ) + + assert result.extra == {"application/vnd.plotly.v1+json": {"data": [{"x": [1, 2]}]}} + + +def test_formats_lists_non_standard_mime_types(): + result = _result_from( + {"text": "hello", "application/vnd.plotly.v1+json": {"data": [{"x": [1, 2]}]}} + ) + + assert "application/vnd.plotly.v1+json" in list(result.formats()) + + +def test_to_json_preserves_non_standard_mime_types(): + execution = Execution() + parse_output( + execution, + json.dumps( + { + "type": "result", + "is_main_result": True, + "text": "hello", + "application/vnd.plotly.v1+json": {"data": [{"x": [1, 2]}]}, + } + ), + ) + + assert json.loads(execution.to_json())["results"] == [ + { + "text": "hello", + "application/vnd.plotly.v1+json": {"data": [{"x": [1, 2]}]}, + } + ] + + +def test_result_without_extra_fields_has_no_extra(): + result = _result_from({"text": "hello"}) + + assert result.extra is None + assert list(result.formats()) == ["text"] + + +def test_explicit_extra_and_unknown_kwargs_are_merged(): + # A non-standard MIME type arriving via the parsed message and an explicit + # `extra` passed to the constructor should both be retained. + result = Result( + text="hello", + extra={"application/vnd.custom+json": {"y": 2}}, + **{"application/vnd.plotly.v1+json": {"data": [{"x": [1, 2]}]}}, + ) + + assert result.extra == { + "application/vnd.custom+json": {"y": 2}, + "application/vnd.plotly.v1+json": {"data": [{"x": [1, 2]}]}, + }