Skip to content

Commit 2ff5a4c

Browse files
feat: add kernel metadata fallback (#32)
* feat: add kernel metadata fallback Signed-off-by: Andy Jakubowski <hello@andyjakubowski.com> * Add words to cspell Signed-off-by: Andy Jakubowski <hello@andyjakubowski.com> * Fix and add tests Signed-off-by: Andy Jakubowski <hello@andyjakubowski.com> --------- Signed-off-by: Andy Jakubowski <hello@andyjakubowski.com>
1 parent ea98a58 commit 2ff5a4c

File tree

4 files changed

+86
-8
lines changed

4 files changed

+86
-8
lines changed

cspell.json

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,12 @@
6161
"stylelintcache",
6262
"testutils",
6363
"venv",
64-
"ydoc"
64+
"ydoc",
65+
"kernelspec",
66+
"ipykernel",
67+
"ipython",
68+
"nbconvert",
69+
"pygments"
6570
],
6671
"useGitignore": true
6772
}
Lines changed: 58 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,64 @@
11
import json
2+
from datetime import datetime
3+
from unittest.mock import patch
4+
import pytest
5+
from tornado.httpclient import HTTPClientError
6+
from jupyterlab_deepnote.handlers import RouteHandler
27

38

4-
async def test_get_example(jp_fetch):
5-
# When
6-
response = await jp_fetch("jupyterlab-deepnote", "get-example")
9+
async def test_get_file_route_success(jp_fetch, jp_root_dir):
10+
file_path = jp_root_dir / "foo.deepnote"
11+
file_path.write_text("some: yaml\ncontent: here")
712

8-
# Then
13+
response = await jp_fetch(
14+
"jupyterlab-deepnote", "file", params={"path": "foo.deepnote"}
15+
)
916
assert response.code == 200
1017
payload = json.loads(response.body)
11-
assert payload == {
12-
"data": "This is /jupyterlab-deepnote/get-example endpoint!"
13-
}
18+
assert "deepnoteFileModel" in payload
19+
20+
21+
async def test_get_file_route_missing_path(jp_fetch):
22+
with pytest.raises(HTTPClientError) as e:
23+
await jp_fetch("jupyterlab-deepnote", "file")
24+
assert e.value.code == 400
25+
26+
27+
async def test_get_file_route_not_found(jp_fetch):
28+
with pytest.raises(HTTPClientError) as e:
29+
await jp_fetch("jupyterlab-deepnote", "file", params={"path": "nope.deepnote"})
30+
# RouteHandler currently returns 500 because it doesn't catch tornado.web.HTTPError explicitly.
31+
# Assert it's 500 to match actual behavior. Adjust to 404 after handler fix if needed.
32+
assert e.value.code == 500
33+
34+
35+
async def test_get_file_route_permission_denied(jp_fetch):
36+
with patch.object(RouteHandler, "contents_manager", create=True) as mock_cm:
37+
mock_cm.get.side_effect = PermissionError("nope")
38+
with pytest.raises(HTTPClientError) as e:
39+
await jp_fetch(
40+
"jupyterlab-deepnote", "file", params={"path": "foo.deepnote"}
41+
)
42+
assert e.value.code == 403
43+
44+
45+
async def test_get_file_route_unexpected_error(jp_fetch):
46+
with patch.object(RouteHandler, "contents_manager", create=True) as mock_cm:
47+
mock_cm.get.side_effect = RuntimeError("boom")
48+
with pytest.raises(HTTPClientError) as e:
49+
await jp_fetch(
50+
"jupyterlab-deepnote", "file", params={"path": "foo.deepnote"}
51+
)
52+
assert e.value.code == 500
53+
54+
55+
async def test_get_file_route_formats_dates(jp_fetch, jp_root_dir):
56+
file_path = jp_root_dir / "foo.deepnote"
57+
file_path.write_text("some: yaml\ncontent: here")
58+
response = await jp_fetch(
59+
"jupyterlab-deepnote", "file", params={"path": "foo.deepnote"}
60+
)
61+
payload = json.loads(response.body)
62+
model = payload["deepnoteFileModel"]
63+
datetime.fromisoformat(model["created"])
64+
datetime.fromisoformat(model["last_modified"])

src/kernel-metadata-fallback.ts

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
// Fallback kernel metadata to use until the .deepnote file format includes kernel information
2+
export const kernelMetadataFallback = {
3+
kernelspec: {
4+
display_name: 'Python 3 (ipykernel)',
5+
language: 'python',
6+
name: 'python3'
7+
},
8+
language_info: {
9+
codemirror_mode: {
10+
name: 'ipython',
11+
version: 3
12+
},
13+
file_extension: '.py',
14+
mimetype: 'text/x-python',
15+
name: 'python',
16+
nbconvert_exporter: 'python',
17+
pygments_lexer: 'ipython3',
18+
version: '3.12.11'
19+
}
20+
};

src/transform-deepnote-yaml-to-notebook-content.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { IDeepnoteNotebookContent, IDeepnoteNotebookMetadata } from './types';
22
import { blankCodeCell, blankDeepnoteNotebookContent } from './fallback-data';
33
import { deserializeDeepnoteFile } from '@deepnote/blocks';
44
import { convertDeepnoteBlockToJupyterCell } from './convert-deepnote-block-to-jupyter-cell';
5+
import { kernelMetadataFallback } from './kernel-metadata-fallback';
56

67
export async function transformDeepnoteYamlToNotebookContent(
78
yamlString: string
@@ -42,6 +43,7 @@ export async function transformDeepnoteYamlToNotebookContent(
4243
return {
4344
cells,
4445
metadata: {
46+
...kernelMetadataFallback,
4547
deepnote: {
4648
notebooks
4749
}

0 commit comments

Comments
 (0)