Skip to content

Commit 56d6b65

Browse files
committed
Addressed review comments
1 parent 3988c65 commit 56d6b65

File tree

4 files changed

+60
-136
lines changed

4 files changed

+60
-136
lines changed

jupyter_ai_tools/toolkits/notebook.py

Lines changed: 18 additions & 102 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,20 @@
1-
import json
21
import asyncio
32
import difflib
4-
3+
import json
4+
import re
55
from typing import Any, Dict, Literal, Optional, Tuple
66

77
import nbformat
88
from jupyter_ai.tools.models import Tool, Toolkit
9-
from pycrdt import Awareness, Doc, Text, Assoc
109
from jupyter_ydoc import YNotebook
10+
from pycrdt import Assoc, Text
1111

12-
from ..utils import cell_to_md, get_file_id, get_jupyter_ydoc, notebook_json_to_md, get_global_awareness, collaborative_tool
13-
import re
12+
from ..utils import (
13+
cell_to_md,
14+
get_file_id,
15+
get_jupyter_ydoc,
16+
notebook_json_to_md,
17+
)
1418

1519

1620
def _is_uuid_like(value: str) -> bool:
@@ -69,7 +73,7 @@ async def read_notebook(file_path: str, include_outputs=False) -> str:
6973
notebook_dict = await read_notebook_json(file_path)
7074
notebook_md = notebook_json_to_md(notebook_dict, include_outputs=include_outputs)
7175
return notebook_md
72-
except Exception as e:
76+
except Exception:
7377
raise
7478

7579

@@ -90,7 +94,7 @@ async def read_notebook_json(file_path: str) -> Dict[str, Any]:
9094
with open(file_path, "r", encoding="utf-8") as f:
9195
notebook_dict = json.load(f)
9296
return notebook_dict
93-
except Exception as e:
97+
except Exception:
9498
raise
9599

96100

@@ -121,7 +125,7 @@ async def read_cell(file_path: str, cell_id: str, include_outputs: bool = True)
121125
cell, cell_index = await read_cell_json(file_path, resolved_cell_id)
122126
cell_md = cell_to_md(cell, cell_index)
123127
return cell_md
124-
except Exception as e:
128+
except Exception:
125129
raise
126130

127131

@@ -157,7 +161,7 @@ async def read_cell_json(file_path: str, cell_id: str) -> Tuple[Dict[str, Any],
157161

158162
raise LookupError(f"No cell found with {cell_id=}")
159163

160-
except Exception as e:
164+
except Exception:
161165
raise
162166

163167

@@ -193,7 +197,7 @@ async def get_cell_id_from_index(file_path: str, cell_index: int) -> str:
193197

194198
return cell_id
195199

196-
except Exception as e:
200+
except Exception:
197201
raise
198202

199203

@@ -269,7 +273,7 @@ async def add_cell(
269273
with open(file_path, "w", encoding="utf-8") as f:
270274
nbformat.write(notebook, f)
271275

272-
except Exception as e:
276+
except Exception:
273277
raise
274278

275279

@@ -333,7 +337,7 @@ async def insert_cell(
333337
with open(file_path, "w", encoding="utf-8") as f:
334338
nbformat.write(notebook, f)
335339

336-
except Exception as e:
340+
except Exception:
337341
raise
338342

339343

@@ -381,7 +385,7 @@ async def delete_cell(file_path: str, cell_id: str):
381385
if cell_index is None:
382386
raise ValueError(f"Could not find cell index for {cell_id=}")
383387

384-
except Exception as e:
388+
except Exception:
385389
raise
386390

387391

@@ -784,7 +788,7 @@ async def edit_cell(file_path: str, cell_id: str, content: str) -> None:
784788
else:
785789
raise ValueError(f"Cell with {cell_id=} not found in notebook at {file_path=}")
786790

787-
except Exception as e:
791+
except Exception:
788792
raise
789793

790794

@@ -903,99 +907,11 @@ def _determine_insert_index(cells_count: int, cell_index: Optional[int], add_abo
903907

904908

905909

906-
907-
# async def set_persona_awareness(
908-
# file_path: str,
909-
# username: str,
910-
# name: str,
911-
# display_name: str,
912-
# initials: str,
913-
# avatar_url: str = "",
914-
# color: str = "var(--jp-collaborator-color1)",
915-
# mention_name: str = "",
916-
# current: str = "",
917-
# documents: list = None
918-
# ) -> str:
919-
# """Sets user awareness information in the notebook's YDoc.
920-
921-
# This function sets both the local and global "user" field in the notebook's
922-
# awareness state with the provided user information based on the Jupyter Server user model.
923-
924-
# Args:
925-
# file_path: The relative path to the notebook file on the filesystem.
926-
# username: The username of the user
927-
# name: The full name of the user
928-
# display_name: The display name for the user
929-
# initials: User's initials for avatar display
930-
# avatar_url: URL to the user's avatar image (optional)
931-
# color: CSS color variable for user identification (optional)
932-
# mention_name: The mention name for @-mentions (optional, defaults to @username)
933-
# current: Current context/status string (optional)
934-
# documents: List of documents the user is working with (optional)
935-
936-
# Returns:
937-
# Success message or error message
938-
# """
939-
# try:
940-
# print(f"DEBUG: set_user_awareness called with file_path='{file_path}', username='{username}'")
941-
942-
# file_id = await get_file_id(file_path)
943-
# ydoc = await get_jupyter_ydoc(file_id)
944-
# global_awareness = await get_global_awareness()
945-
946-
# if not ydoc:
947-
# return f"Error: Could not access notebook document for {file_path}. Notebook may not be open."
948-
949-
# # Set default mention_name if not provided
950-
# if not mention_name:
951-
# mention_name = f"@{username}"
952-
953-
# # Set default documents list if not provided
954-
# if documents is None:
955-
# documents = [file_path]
956-
957-
# # Create user model based on Jupyter Server user model
958-
# user_model = {
959-
# "username": username,
960-
# "name": name,
961-
# "display_name": display_name,
962-
# "initials": initials,
963-
# "avatar_url": avatar_url,
964-
# "color": color,
965-
# "mention_name": mention_name
966-
# }
967-
968-
# print(f"DEBUG: set_user_awareness setting user awareness with model: {user_model}")
969-
970-
# # Set the local user field in the notebook's awareness
971-
# ydoc.awareness.set_local_state_field("user", user_model)
972-
973-
# # Create global awareness state with user, current, and documents fields
974-
# global_state = {
975-
# "user": user_model,
976-
# "current": current,
977-
# "documents": documents
978-
# }
979-
980-
# print(f"DEBUG: set_user_awareness setting global awareness state: {global_state}")
981-
982-
# # Set the global awareness state
983-
# global_awareness.set_local_state(global_state)
984-
985-
# print("DEBUG: set_user_awareness completed successfully")
986-
# return f"Successfully set user awareness for {display_name} ({username}) in notebook {file_path} with current='{current}' and {len(documents)} documents"
987-
988-
# except Exception as e:
989-
# print(f"ERROR: set_user_awareness failed for {file_path}, username={username}: {str(e)}")
990-
# return f"Error setting user awareness: {str(e)}"
991-
992-
993910
toolkit = Toolkit(
994911
name="notebook_toolkit",
995912
description="Tools for reading and manipulating Jupyter notebooks.",
996913
)
997914
toolkit.add_tool(Tool(callable=read_notebook, read=True))
998-
# toolkit.add_tool(Tool(callable=write_to_cell, read=True, write=True))
999915
toolkit.add_tool(Tool(callable=read_cell, read=True))
1000916
toolkit.add_tool(Tool(callable=add_cell, read=True, write=True))
1001917
toolkit.add_tool(Tool(callable=insert_cell, read=True, write=True))

jupyter_ai_tools/utils.py

Lines changed: 35 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
1-
from jupyter_server.serverapp import ServerApp
2-
from pycrdt import Awareness
3-
import inspect
41
import functools
2+
import inspect
53
import typing
64
from typing import Optional
75

6+
from jupyter_server.serverapp import ServerApp
7+
from jupyter_server.auth.identity import User
8+
from pycrdt import Awareness
9+
810

911
async def get_serverapp():
1012
"""Returns the server app from the request context"""
@@ -63,7 +65,7 @@ async def get_file_id(file_path: str) -> str:
6365
return file_id
6466

6567

66-
def collaborative_tool(user: typing.Optional[typing.Dict[str, typing.Any]] = None):
68+
def collaborative_tool(user: User):
6769
"""
6870
Decorator factory to enable collaborative awareness for toolkit functions.
6971
@@ -95,8 +97,13 @@ def decorator(tool_func):
9597
@functools.wraps(tool_func)
9698
async def wrapper(*args, **kwargs):
9799
# Skip awareness if no user provided
98-
if user is None:
99-
return await tool_func(*args, **kwargs)
100+
101+
# Get serverapp for logging
102+
try:
103+
serverapp = await get_serverapp()
104+
logger = serverapp.log
105+
except Exception:
106+
logger = None
100107

101108
# Extract file_path from tool function arguments for notebook-specific awareness
102109
file_path = None
@@ -112,24 +119,24 @@ async def wrapper(*args, **kwargs):
112119
# Look for file_path parameter
113120
if 'file_path' in param_names and len(args) > param_names.index('file_path'):
114121
file_path = args[param_names.index('file_path')]
115-
116-
# Set notebook-specific collaborative awareness if we have a file_path
117-
if file_path and file_path.endswith('.ipynb'):
118-
try:
119-
file_id = await get_file_id(file_path)
120-
ydoc = await get_jupyter_ydoc(file_id)
121-
122-
if ydoc:
123-
# Set the local user field in the notebook's awareness
124-
ydoc.awareness.set_local_state_field("user", user)
125-
126-
except Exception:
127-
# Log but don't block tool execution
128-
pass
129-
130-
except Exception:
131-
# Catch any errors in file_path detection
132-
pass
122+
except Exception as e:
123+
# Log error in file_path detection
124+
if logger:
125+
logger.warning(f"Error detecting file_path in collaborative_tool: {e}")
126+
127+
# Set notebook-specific collaborative awareness if we have a file_path
128+
if file_path and file_path.endswith('.ipynb'):
129+
try:
130+
file_id = await get_file_id(file_path)
131+
ydoc = await get_jupyter_ydoc(file_id)
132+
133+
if ydoc:
134+
# Set the local user field in the notebook's awareness
135+
ydoc.awareness.set_local_state_field("user", user)
136+
except Exception as e:
137+
# Log error but don't block tool execution
138+
if logger:
139+
logger.warning(f"Error setting notebook awareness in collaborative_tool: {e}")
133140

134141
# Set global awareness
135142
try:
@@ -140,9 +147,10 @@ async def wrapper(*args, **kwargs):
140147
"current": file_path or "",
141148
"documents": [file_path] if file_path else []
142149
})
143-
except Exception:
144-
# Log but don't block tool execution
145-
pass
150+
except Exception as e:
151+
# Log error but don't block tool execution
152+
if logger:
153+
logger.warning(f"Error setting global awareness in collaborative_tool: {e}")
146154

147155
# Execute the original tool function
148156
return await tool_func(*args, **kwargs)

tests/test_collaborative_tool.py

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,7 @@
1-
import pytest
2-
from unittest.mock import AsyncMock, MagicMock, patch
31
import inspect
4-
import asyncio
5-
from typing import Dict, Any, Optional
2+
from unittest.mock import MagicMock, patch
3+
4+
import pytest
65

76
from jupyter_ai_tools.utils import collaborative_tool
87

tests/test_write_to_cell_collaboratively.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,16 @@
22
Unit tests for the write_to_cell_collaboratively function and its helper functions.
33
"""
44

5+
from unittest.mock import MagicMock, Mock, patch
6+
57
import pytest
6-
import asyncio
7-
from unittest.mock import Mock, MagicMock, AsyncMock, patch
8+
89
from jupyter_ai_tools.toolkits.notebook import (
9-
write_to_cell_collaboratively,
1010
_handle_delete_operation,
1111
_handle_insert_operation,
1212
_handle_replace_operation,
1313
_safe_set_cursor,
14+
write_to_cell_collaboratively,
1415
)
1516

1617

0 commit comments

Comments
 (0)