Skip to content

Commit

Permalink
Add optional title argument to %view (#2787)
Browse files Browse the repository at this point in the history
Also removed the ability to call `%view` or `%connection_show` with an expression, and added more helpful error messages.
  • Loading branch information
seeM authored Apr 18, 2024
1 parent edddc2f commit 25fff84
Show file tree
Hide file tree
Showing 9 changed files with 192 additions and 93 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -181,7 +181,7 @@ def _wrap_connection(self, obj: Any) -> Connection:
elif safe_isinstance(obj, "sqlalchemy", "Engine"):
return SQLAlchemyConnection(obj)

raise ValueError(f"Unsupported connection type {type_name}")
raise TypeError(f"Unsupported connection type {type_name}")

def _close_connection(self, comm_id: str):
try:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,15 +39,15 @@ class FigureManagerPositron(FigureManagerBase):
"""
Interface for the matplotlib backend to interact with the Positron frontend.
Parameters:
-----------
Parameters
----------
canvas
The canvas for this figure.
num
The figure number.
Attributes:
-----------
Attributes
----------
canvas
The canvas for this figure.
"""
Expand Down Expand Up @@ -110,13 +110,13 @@ class FigureCanvasPositron(FigureCanvasAgg):
"""
The canvas for a figure in the Positron backend.
Parameters:
-----------
Parameters
----------
figure
The figure to draw on this canvas.
Attributes:
-----------
Attributes
----------
manager
The manager for this canvas.
"""
Expand All @@ -135,8 +135,8 @@ def draw(self, is_rendering=False) -> None:
"""
Draw the canvas; send an update event if the canvas has changed.
Parameters:
-----------
Parameters
----------
is_rendering
Whether the canvas is being rendered, to avoid recursively requesting an update from the
frontend.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,8 @@ class Plot:
"""
The backend representation of a frontend plot instance.
Paramaters:
-----------
Paramaters
----------
comm
The communication channel to the frontend plot instance.
render
Expand Down Expand Up @@ -121,8 +121,8 @@ class PlotsService:
"""
The plots service is responsible for managing `Plot` instances.
Paramaters:
-----------
Paramaters
----------
target_name
The name of the target for plot comms, as defined in the frontend.
session_mode
Expand Down Expand Up @@ -154,8 +154,8 @@ def close_plot(self, plot: Plot) -> None:
"""
Close a plot.
Parameters:
-----------
Parameters
----------
plot
The plot to close.
"""
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -71,13 +71,13 @@ class PositronComm:
"""
A wrapper around a base IPython comm that provides a JSON-RPC interface.
Paramaters:
-----------
Paramaters
----------
comm
The wrapped IPython comm.
Attributes:
-----------
Attributes
----------
comm
The wrapped IPython comm.
comm_id
Expand All @@ -91,8 +91,8 @@ def create(cls, target_name: str, comm_id: str) -> PositronComm:
"""
Create a Positron comm.
Parameters:
-----------
Parameters
----------
target_name
The name of the target for the comm, as defined in the frontend.
comm_id
Expand Down Expand Up @@ -132,8 +132,8 @@ def on_msg(
"""
Register a callback for an RPC request from the frontend.
Parameters:
-----------
Parameters
----------
callback
Called when a message is received, with both the parsed message `msg: CommMessage` and
original `raw_msg`. Not called if the `raw_msg` could not be parsed; instead, a JSON-RPC
Expand Down Expand Up @@ -191,8 +191,8 @@ def send_result(self, data: JsonData = None, metadata: Optional[JsonRecord] = No
"""
Send a JSON-RPC result to the frontend-side version of this comm.
Parameters:
-----------
Parameters
----------
data
The result data to send.
metadata
Expand All @@ -212,8 +212,8 @@ def send_event(self, name: str, payload: JsonRecord) -> None:
"""
Send a JSON-RPC notification (event) to the frontend-side version of this comm.
Parameters:
-----------
Parameters
----------
name
The name of the event.
payload
Expand All @@ -230,8 +230,8 @@ def send_error(self, code: JsonRpcErrorCode, message: Optional[str] = None) -> N
"""
Send a JSON-RPC result to the frontend-side version of this comm.
Parameters:
-----------
Parameters
----------
code
The error code to send.
message
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,15 @@
from ipykernel.ipkernel import IPythonKernel
from ipykernel.kernelapp import IPKernelApp
from ipykernel.zmqshell import ZMQDisplayPublisher, ZMQInteractiveShell
from IPython.core import oinspect, page
from IPython.core import magic_arguments, oinspect, page
from IPython.core.error import UsageError
from IPython.core.interactiveshell import ExecutionInfo, InteractiveShell
from IPython.core.magic import Magics, MagicsManager, line_magic, magics_class, needs_local_scope
from IPython.core.magic import (
Magics,
MagicsManager,
line_magic,
magics_class,
)
from IPython.utils import PyColorize

from .connections import ConnectionsService
Expand All @@ -31,7 +37,7 @@
from .plots import PlotsService
from .session_mode import SessionMode
from .ui import UiService
from .utils import JsonRecord
from .utils import JsonRecord, get_qualname
from .variables import VariablesService
from .widget import PositronWidgetHook

Expand Down Expand Up @@ -96,34 +102,90 @@ def pinfo(
class PositronMagics(Magics):
shell: PositronShell

# This will override the default `clear` defined in `ipykernel.zmqshell.KernelMagics`.
@line_magic
def clear(self, line: str) -> None: # type: ignore reportIncompatibleMethodOverride
def clear(self, line: str) -> None:
"""Clear the console."""
# Send a message to the frontend to clear the console.
self.shell.kernel.ui_service.clear_console()

@needs_local_scope
@magic_arguments.magic_arguments()
@magic_arguments.argument(
"object",
help="The object to view.",
)
@magic_arguments.argument(
"title",
nargs="?",
help="The title of the Data Explorer tab. Defaults to the object's name.",
)
@line_magic
def view(self, line: str, local_ns: Dict[str, Any]):
"""View an object in the Positron Data Explorer."""
try:
obj = local_ns[line]
except KeyError: # not in namespace
obj = eval(line, local_ns, local_ns)
def view(self, line: str) -> None:
"""
View an object in the Positron Data Explorer.
# Register a dataset with the dataviewer service.
self.shell.kernel.data_explorer_service.register_table(obj, line)
Examples
--------
View an object:
@needs_local_scope
>>> %view df
View an object with a custom title (quotes are required if the title contains spaces):
>>> %view df "My Dataset"
"""
try:
args = magic_arguments.parse_argstring(self.view, line)
except UsageError as e:
if (
len(e.args) > 0
and isinstance(e.args[0], str)
and e.args[0].startswith("unrecognized arguments")
):
raise UsageError(f"{e.args[0]}. Did you quote the title?")
raise

# Find the object.
info = self.shell._ofind(args.object)
if not info.found:
raise UsageError(f"name '{args.object}' is not defined")

title = args.title
if title is None:
title = args.object
else:
# Remove quotes around the title if they exist.
if (title.startswith('"') and title.endswith('"')) or (
title.startswith("'") and title.endswith("'")
):
title = title[1:-1]

# Register a dataset with the data explorer service.
obj = info.obj
try:
self.shell.kernel.data_explorer_service.register_table(obj, title)
except TypeError:
raise UsageError(f"cannot view object of type '{get_qualname(obj)}'")

@magic_arguments.magic_arguments()
@magic_arguments.argument(
"object",
help="The connection object to show.",
)
@line_magic
def connection_show(self, line: str, local_ns: Dict[str, Any]):
def connection_show(self, line: str) -> None:
"""Show a connection object in the Positron Connections Pane."""
try:
obj = local_ns[line]
except KeyError: # not in namespace
obj = eval(line, local_ns, local_ns)
args = magic_arguments.parse_argstring(self.connection_show, line)

# Find the object.
info = self.shell._ofind(args.object)
if not info.found:
raise UsageError(f"name '{args.object}' is not defined")

self.shell.kernel.connections_service.register_connection(obj)
try:
self.shell.kernel.connections_service.register_connection(info.obj)
except TypeError:
raise UsageError(f"cannot show object of type '{get_qualname(info.obj)}'")


_traceback_file_link_re = re.compile(r"^(File \x1b\[\d+;\d+m)(.+):(\d+)")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,13 @@ def shell() -> Iterable[PositronShell]:
shell.reset()


@pytest.fixture
def mock_connections_service(shell: PositronShell, monkeypatch: pytest.MonkeyPatch) -> Mock:
mock = Mock()
monkeypatch.setattr(shell.kernel, "connections_service", mock)
return mock


@pytest.fixture
def mock_dataexplorer_service(shell: PositronShell, monkeypatch: pytest.MonkeyPatch) -> Mock:
mock = Mock()
Expand Down
Loading

0 comments on commit 25fff84

Please sign in to comment.