Skip to content

Commit 38025af

Browse files
Add custom API route for .deepnote files
Signed-off-by: Andy Jakubowski <[email protected]>
1 parent feaabc5 commit 38025af

File tree

6 files changed

+33
-94
lines changed

6 files changed

+33
-94
lines changed

README.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -114,12 +114,12 @@ jlpm run watch
114114

115115
The `jlpm` command is JupyterLab's pinned version of
116116
[yarn](https://yarnpkg.com/) that is installed with JupyterLab. You may use
117-
`yarn` or `npm` in lieu of `jlpm` below.
117+
`yarn` or `npm` instead of `jlpm` below.
118118

119-
In a separate terminal, run `jupyter lab` with the `--config` option to register our custom file contents manager for the `.deepnote` extension. The `--debug` option lets you see HTTP requests in the logs, which is helpful for debugging.
119+
In a separate terminal, run `jupyter lab`. You can add the `--debug` option to see HTTP requests in the logs, which can be helpful for debugging.
120120

121121
```shell
122-
jupyter lab --debug --config="$(pwd)/jupyter-config/server-config/jupyter_server_config.json"
122+
jupyter lab --debug
123123
```
124124

125125
You can watch the source directory and run JupyterLab at the same time in different terminals to watch for changes in the extension's source and automatically rebuild the extension.

jupyter-config/server-config/jupyter_server_config.json

Lines changed: 0 additions & 5 deletions
This file was deleted.

jupyterlab_deepnote/__init__.py

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@
88

99
warnings.warn("Importing 'jupyterlab_deepnote' outside a proper installation.")
1010
__version__ = "dev"
11-
from jupyterlab_deepnote.contents import DeepnoteContentsManager
1211
from .handlers import setup_handlers
1312

1413

@@ -31,4 +30,3 @@ def _load_jupyter_server_extension(server_app):
3130
setup_handlers(server_app.web_app)
3231
name = "jupyterlab_deepnote"
3332
server_app.log.info(f"Registered {name} server extension")
34-
server_app.contents_manager_class = DeepnoteContentsManager

jupyterlab_deepnote/contents.py

Lines changed: 0 additions & 40 deletions
This file was deleted.

jupyterlab_deepnote/handlers.py

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
from datetime import datetime
12
import json
23

34
from jupyter_server.base.handlers import APIHandler
@@ -12,20 +13,26 @@ class RouteHandler(APIHandler):
1213
# Jupyter server
1314
@tornado.web.authenticated
1415
async def get(self):
15-
print("🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨 YOOOOO")
1616
path = self.get_query_argument("path")
1717
# Use Jupyter Server’s contents_manager, not direct filesystem access.
1818
model = await ensure_async(
1919
self.contents_manager.get(path, type="file", format="text", content=True)
2020
)
21-
result = {"data": model, "path": path}
21+
# Convert datetimes to strings so JSON can handle them
22+
for key in ("created", "last_modified"):
23+
if isinstance(model.get(key), datetime):
24+
model[key] = model[key].isoformat()
25+
26+
# Return everything, including YAML content
27+
result = {"deepnoteFileModel": model}
28+
2229
self.finish(json.dumps(result))
2330

2431

2532
def setup_handlers(web_app):
2633
host_pattern = ".*$"
2734

2835
base_url = web_app.settings["base_url"]
29-
route_pattern = url_path_join(base_url, "api", "contents", "resolve")
36+
route_pattern = url_path_join(base_url, "jupyterlab-deepnote", "file")
3037
handlers = [(route_pattern, RouteHandler)]
3138
web_app.add_handlers(host_pattern, handlers)

src/deepnote-content-provider.ts

Lines changed: 20 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -1,63 +1,42 @@
11
import { Contents, RestContentProvider } from '@jupyterlab/services';
2-
import { z } from 'zod';
32
import { transformDeepnoteYamlToNotebookContent } from './transform-deepnote-yaml-to-notebook-content';
3+
import { requestAPI } from './handler';
44

55
export const deepnoteContentProviderName = 'deepnote-content-provider';
6-
7-
const deepnoteFileFromServerSchema = z.object({
8-
cells: z.array(z.any()), // or refine further with nbformat
9-
metadata: z.object({
10-
deepnote: z.object({
11-
rawYamlString: z.string()
12-
})
13-
}),
14-
nbformat: z.number(),
15-
nbformat_minor: z.number()
16-
});
17-
186
export class DeepnoteContentProvider extends RestContentProvider {
197
async get(
208
localPath: string,
219
options?: Contents.IFetchOptions
2210
): Promise<Contents.IModel> {
2311
const isDeepnoteFile = localPath.endsWith('.deepnote');
2412

25-
if (isDeepnoteFile) {
26-
await super.get(`resolve?path=${localPath}`, options);
27-
}
28-
29-
const model = await super.get(localPath, options);
30-
3113
if (!isDeepnoteFile) {
3214
// Not a .deepnote file, return as-is
33-
return model;
15+
const nonDeepnoteModel = await super.get(localPath, options);
16+
return nonDeepnoteModel;
3417
}
3518

36-
const validatedModelContent = deepnoteFileFromServerSchema.safeParse(
37-
model.content
38-
);
39-
40-
if (!validatedModelContent.success) {
41-
console.error(
42-
'Invalid .deepnote file content:',
43-
validatedModelContent.error
44-
);
45-
// Return an empty notebook instead of throwing an error
46-
model.content.cells = [];
47-
return model;
48-
}
19+
// Call custom API route to fetch the Deepnote file content
20+
const data = await requestAPI<any>(`file?path=${localPath}`);
21+
const modelData = data.deepnoteFileModel;
4922

5023
// Transform the Deepnote YAML to Jupyter notebook content
51-
const transformedModelContent =
52-
await transformDeepnoteYamlToNotebookContent(
53-
validatedModelContent.data.metadata.deepnote.rawYamlString
54-
);
24+
const notebookContent = await transformDeepnoteYamlToNotebookContent(
25+
modelData.content
26+
);
5527

56-
const transformedModel = {
57-
...model,
58-
content: transformedModelContent
28+
const model: Contents.IModel = {
29+
name: modelData.name,
30+
path: modelData.path,
31+
type: 'notebook',
32+
writable: false,
33+
created: modelData.created,
34+
last_modified: modelData.last_modified,
35+
mimetype: 'application/x-ipynb+json',
36+
format: 'json',
37+
content: notebookContent
5938
};
6039

61-
return transformedModel;
40+
return model;
6241
}
6342
}

0 commit comments

Comments
 (0)