From 167eaa33f5db92f7236817fe8a3fd2c4fb00c5f8 Mon Sep 17 00:00:00 2001 From: DevDesai-444 <109354742+DevDesai444@users.noreply.github.com> Date: Fri, 30 Oct 2026 15:16:58 -0400 Subject: [PATCH 1/2] fix(download): serialize JSON format content with json.dumps The Contents API returns notebooks with format=json as a parsed dict. Using str() on this dict produces Python repr with single-quoted keys, which is not valid JSON. Downloaded notebooks could not be reopened. Use json.dumps() for the json format case so the output is valid JSON. Fixes #190. --- src/colab_cli/contents.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/colab_cli/contents.py b/src/colab_cli/contents.py index 72d6d06c..d4d0f630 100644 --- a/src/colab_cli/contents.py +++ b/src/colab_cli/contents.py @@ -13,6 +13,7 @@ # limitations under the License. import base64 +import json from urllib.parse import quote import requests @@ -82,8 +83,9 @@ def download(self, remote_path: str, local_path: str): if fmt == "base64": content_bytes = base64.b64decode(content) + elif fmt == "json": + content_bytes = json.dumps(content, indent=1).encode("utf-8") else: - # Assume text if it's not base64 explicitly encoded content_bytes = str(content).encode("utf-8") with open(local_path, "wb") as f: From 3111a1b9db4809d30a670936bd7c448e7afc78dc Mon Sep 17 00:00:00 2001 From: DevDesai-444 <109354742+DevDesai444@users.noreply.github.com> Date: Fri, 30 Oct 2026 15:17:02 -0400 Subject: [PATCH 2/2] test(download): verify JSON format produces valid output Adds test_download_json_format_writes_valid_json to confirm that notebooks returned with format=json round-trip through json.loads and match the original content dict. --- tests/test_contents.py | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/tests/test_contents.py b/tests/test_contents.py index 4c910c88..713aaff3 100644 --- a/tests/test_contents.py +++ b/tests/test_contents.py @@ -13,6 +13,7 @@ # limitations under the License. import base64 +import json from unittest.mock import MagicMock, patch import pytest @@ -123,6 +124,33 @@ def test_download_file(mock_request, client, tmp_path): assert local_file.read_bytes() == content_bytes +@patch("colab_cli.contents.requests.request") +def test_download_json_format_writes_valid_json(mock_request, client, tmp_path): + """Notebooks returned with format=json must be saved as valid JSON, not Python repr.""" + notebook_content = { + "cells": [{"cell_type": "code", "source": ["print('hello')"]}], + "metadata": {}, + "nbformat": 4, + "nbformat_minor": 5, + } + mock_resp = MagicMock(spec=Response) + mock_resp.status_code = 200 + mock_resp.json.return_value = { + "name": "notebook.ipynb", + "type": "notebook", + "format": "json", + "content": notebook_content, + } + mock_request.return_value = mock_resp + + local_file = tmp_path / "notebook.ipynb" + client.download("content/notebook.ipynb", str(local_file)) + + raw = local_file.read_text("utf-8") + parsed = json.loads(raw) + assert parsed == notebook_content + + @patch("colab_cli.contents.requests.request") def test_upload_file(mock_request, client, tmp_path): mock_resp = MagicMock(spec=Response)