Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions docs/03_file_management.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ File management on the Colab VM will be implemented using the Jupyter Contents A
### 3. Downloading Files (`colab download`)
- **API**: `GET /api/contents/<remote_path>?content=1` (as seen in HAR).
- **Response**: JSON with `content` field.
- **Handling**: Decodes content based on `format` (text or base64) and saves it locally.
- **Handling**: Decodes content based on `format` (`base64`, `json`, or plain `text`) and saves it locally.

### 4. Deleting Files (`colab rm`)
- **API**: `DELETE /api/contents/<remote_path>`.
Expand Down Expand Up @@ -59,4 +59,4 @@ TDD is mandatory for all file management features.

### 2. Error Cases
- **Test Case**: Verify 404 responses are correctly caught and presented as a "File not found" error to the user.
- **Test Case**: Verify correct handling of large file uploads exceeding API limits via kernel streaming.
- **Test Case**: Verify correct handling of large file uploads exceeding API limits via kernel streaming.
3 changes: 3 additions & 0 deletions src/colab_cli/contents.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
# limitations under the License.

import base64
import json
from urllib.parse import quote

import requests
Expand Down Expand Up @@ -82,6 +83,8 @@ 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=2).encode("utf-8")
else:
# Assume text if it's not base64 explicitly encoded
content_bytes = str(content).encode("utf-8")
Expand Down
37 changes: 37 additions & 0 deletions tests/test_contents.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
# limitations under the License.

import base64
import json
from unittest.mock import MagicMock, patch

import pytest
Expand Down Expand Up @@ -123,6 +124,42 @@ 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_notebook_json_format(mock_request, client, tmp_path):
"""Downloading a format=json payload serializes valid 2-space indented JSON."""
notebook_doc = {
"cells": [
{"cell_type": "markdown", "metadata": {}, "source": ["# Demo"]},
{
"cell_type": "code",
"execution_count": 1,
"metadata": {},
"outputs": [],
"source": ["x = 42\n", "print(x)"],
},
],
"metadata": {"kernelspec": {"display_name": "Python 3", "name": "python3"}},
"nbformat": 4,
"nbformat_minor": 5,
}
mock_resp = MagicMock(spec=Response)
mock_resp.status_code = 200
mock_resp.json.return_value = {
"name": "demo.ipynb",
"type": "notebook",
"format": "json",
"content": notebook_doc,
}
mock_request.return_value = mock_resp

local_file = tmp_path / "demo.ipynb"
client.download("content/demo.ipynb", str(local_file))

raw = local_file.read_text(encoding="utf-8")
assert json.loads(raw) == notebook_doc
assert raw == json.dumps(notebook_doc, indent=2)


@patch("colab_cli.contents.requests.request")
def test_upload_file(mock_request, client, tmp_path):
mock_resp = MagicMock(spec=Response)
Expand Down
Loading