carefully setup shell + rename
This commit is contained in:
@@ -1,194 +0,0 @@
|
|||||||
from collections import defaultdict
|
|
||||||
from datetime import UTC, datetime
|
|
||||||
from uuid import uuid4
|
|
||||||
|
|
||||||
from IPython.core.interactiveshell import InteractiveShell
|
|
||||||
from IPython.core.pylabtools import (
|
|
||||||
activate_matplotlib,
|
|
||||||
import_pylab,
|
|
||||||
mpl_runner,
|
|
||||||
select_figure_formats,
|
|
||||||
)
|
|
||||||
from matplotlib_inline.backend_inline import configure_inline_support
|
|
||||||
|
|
||||||
from .events import (
|
|
||||||
error_from_execution_result,
|
|
||||||
event_from_execution_result,
|
|
||||||
event_from_history_output,
|
|
||||||
)
|
|
||||||
from .models import CallEvent, CallResult, ShellInfo, ShellStatus
|
|
||||||
|
|
||||||
|
|
||||||
class Shell:
|
|
||||||
"""Own one configured, in-process IPython shell."""
|
|
||||||
|
|
||||||
def __init__(self, name: str):
|
|
||||||
self.name = name
|
|
||||||
self.shell_id = uuid4().hex
|
|
||||||
self.created_at = datetime.now(UTC).isoformat()
|
|
||||||
self.last_used_at: str | None = None
|
|
||||||
self.status: ShellStatus = "ready"
|
|
||||||
self._last_execution_count = 0
|
|
||||||
self._shell: InteractiveShell | None = None
|
|
||||||
|
|
||||||
@property
|
|
||||||
def shell(self) -> InteractiveShell:
|
|
||||||
if self._shell is not None:
|
|
||||||
return self._shell
|
|
||||||
return self.init_shell()
|
|
||||||
|
|
||||||
def init_shell(self) -> InteractiveShell:
|
|
||||||
if self._shell is not None:
|
|
||||||
return self._shell
|
|
||||||
|
|
||||||
self._shell = InteractiveShell()
|
|
||||||
setup_shell(self._shell, plt=True, pylab=True)
|
|
||||||
return self._shell
|
|
||||||
|
|
||||||
def run_cell(
|
|
||||||
self,
|
|
||||||
code: str,
|
|
||||||
*,
|
|
||||||
call_id: str,
|
|
||||||
) -> tuple[CallResult, list[CallEvent]]:
|
|
||||||
"""Execute one cell and return its result plus ordered output events."""
|
|
||||||
self.status = "running"
|
|
||||||
try:
|
|
||||||
call_result, events = run_cell_and_collect(
|
|
||||||
self.shell,
|
|
||||||
code,
|
|
||||||
call_id=call_id,
|
|
||||||
)
|
|
||||||
except Exception:
|
|
||||||
self.status = "error"
|
|
||||||
raise
|
|
||||||
else:
|
|
||||||
self.status = "ready"
|
|
||||||
self.last_used_at = datetime.now(UTC).isoformat()
|
|
||||||
self._last_execution_count = call_result.execution_count or 0
|
|
||||||
return call_result, events
|
|
||||||
|
|
||||||
def describe(self) -> ShellInfo:
|
|
||||||
"""Return metadata without initializing an unused shell."""
|
|
||||||
return ShellInfo(
|
|
||||||
shell_id=self.shell_id,
|
|
||||||
name=self.name,
|
|
||||||
status=self.status,
|
|
||||||
execution_count=self._last_execution_count,
|
|
||||||
created_at=self.created_at,
|
|
||||||
last_used_at=self.last_used_at,
|
|
||||||
capabilities=(
|
|
||||||
"stdout",
|
|
||||||
"stderr",
|
|
||||||
"display_data",
|
|
||||||
"execute_result",
|
|
||||||
"error",
|
|
||||||
"matplotlib-inline",
|
|
||||||
),
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def isolate_output_history(shell: InteractiveShell) -> None:
|
|
||||||
"""Give this shell an instance-owned output ledger.
|
|
||||||
|
|
||||||
IPython declares ``HistoryManager.outputs`` on the class. Shadowing it
|
|
||||||
during shell setup prevents output records from leaking across shells.
|
|
||||||
"""
|
|
||||||
history_manager = shell.history_manager
|
|
||||||
if history_manager is None:
|
|
||||||
raise RuntimeError("InteractiveShell has no history manager")
|
|
||||||
history_manager.outputs = defaultdict(list)
|
|
||||||
|
|
||||||
|
|
||||||
def run_cell_and_collect(
|
|
||||||
shell: InteractiveShell,
|
|
||||||
code: str,
|
|
||||||
*,
|
|
||||||
call_id: str,
|
|
||||||
) -> tuple[CallResult, list[CallEvent]]:
|
|
||||||
"""Run one cell and snapshot its output records after execution."""
|
|
||||||
history_manager = shell.history_manager
|
|
||||||
if history_manager is None:
|
|
||||||
raise RuntimeError("InteractiveShell has no history manager")
|
|
||||||
if "outputs" not in history_manager.__dict__:
|
|
||||||
isolate_output_history(shell)
|
|
||||||
|
|
||||||
execution = shell.run_cell(code, store_history=True)
|
|
||||||
records = (
|
|
||||||
tuple(history_manager.outputs.get(execution.execution_count, ()))
|
|
||||||
if execution.execution_count is not None
|
|
||||||
else ()
|
|
||||||
)
|
|
||||||
events = [
|
|
||||||
event_from_history_output(
|
|
||||||
record,
|
|
||||||
call_id=call_id,
|
|
||||||
sequence=sequence,
|
|
||||||
)
|
|
||||||
for sequence, record in enumerate(records)
|
|
||||||
]
|
|
||||||
events = [event for event in events if event is not None]
|
|
||||||
|
|
||||||
error = error_from_execution_result(execution)
|
|
||||||
if error is not None:
|
|
||||||
error_event = event_from_execution_result(
|
|
||||||
execution,
|
|
||||||
call_id=call_id,
|
|
||||||
sequence=len(events),
|
|
||||||
)
|
|
||||||
if error_event is not None:
|
|
||||||
events.append(error_event)
|
|
||||||
|
|
||||||
return (
|
|
||||||
CallResult(
|
|
||||||
call_id=call_id,
|
|
||||||
execution_count=execution.execution_count,
|
|
||||||
result=execution.result,
|
|
||||||
error=error,
|
|
||||||
),
|
|
||||||
events,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def setup_shell(shell: InteractiveShell, plt: bool = False, pylab: bool = True):
|
|
||||||
"""Configure an ``InteractiveShell`` without requiring GUI support.
|
|
||||||
|
|
||||||
``InteractiveShell.enable_matplotlib`` and ``enable_pylab`` eventually
|
|
||||||
call ``enable_gui``. The base shell intentionally does not implement that
|
|
||||||
method, so the lower-level matplotlib-inline setup is used here instead.
|
|
||||||
"""
|
|
||||||
history_manager = shell.history_manager
|
|
||||||
if history_manager is not None:
|
|
||||||
history_manager.enabled = False
|
|
||||||
|
|
||||||
# ``HistoryManager.outputs`` is class-level in the IPython version used
|
|
||||||
# here. Give every managed shell an instance-local mapping so execution
|
|
||||||
# records cannot leak between Shell objects.
|
|
||||||
if "outputs" not in history_manager.__dict__:
|
|
||||||
isolate_output_history(shell)
|
|
||||||
|
|
||||||
backend = "module://matplotlib_inline.backend_inline"
|
|
||||||
|
|
||||||
# This is the non-GUI part of enable_matplotlib().
|
|
||||||
if plt:
|
|
||||||
activate_matplotlib(backend)
|
|
||||||
shell.magics_manager.registry["ExecutionMagics"].default_runner = mpl_runner(
|
|
||||||
shell.safe_execfile
|
|
||||||
)
|
|
||||||
|
|
||||||
# This connects matplotlib-inline to THIS shell.
|
|
||||||
configure_inline_support(shell, backend)
|
|
||||||
|
|
||||||
# Optional explicit format selection; configure_inline_support()
|
|
||||||
# also sets up the configured default formats.
|
|
||||||
select_figure_formats(
|
|
||||||
shell,
|
|
||||||
{"svg", "pdf", "png", "jpg"},
|
|
||||||
)
|
|
||||||
if pylab:
|
|
||||||
# Match enable_pylab's namespace behavior without its GUI activation:
|
|
||||||
# hidden names are used by %who/%whos bookkeeping as well as by code.
|
|
||||||
pylab_ns: dict[str, object] = {}
|
|
||||||
import_pylab(pylab_ns, import_all=False) # no import * allowed
|
|
||||||
shell.user_ns.update(pylab_ns)
|
|
||||||
shell.user_ns_hidden.update(pylab_ns)
|
|
||||||
@@ -5,11 +5,11 @@ from typing import Annotated, Literal
|
|||||||
from fastmcp import FastMCP
|
from fastmcp import FastMCP
|
||||||
from pydantic import Field
|
from pydantic import Field
|
||||||
|
|
||||||
import ipython_demo
|
import ipython_shell
|
||||||
from ipython_demo.models import AppInfo, CallOptions, CallResponse, ShellInfo
|
from ipython_shell.models import AppInfo, CallOptions, CallResponse, ShellInfo
|
||||||
|
|
||||||
app = FastMCP(name="IPython Demo")
|
app = FastMCP(name="IPython Demo")
|
||||||
shell_app = ipython_demo.App()
|
shell_app = ipython_shell.App()
|
||||||
|
|
||||||
|
|
||||||
@app.tool(title="Get app info")
|
@app.tool(title="Get app info")
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ from .models import (
|
|||||||
from .shell import (
|
from .shell import (
|
||||||
Shell,
|
Shell,
|
||||||
isolate_output_history,
|
isolate_output_history,
|
||||||
|
carefully_setup_shell,
|
||||||
run_cell_and_collect,
|
run_cell_and_collect,
|
||||||
setup_shell,
|
setup_shell,
|
||||||
)
|
)
|
||||||
@@ -41,6 +42,7 @@ __all__ = [
|
|||||||
"Shell",
|
"Shell",
|
||||||
"ShellInfo",
|
"ShellInfo",
|
||||||
"ShellStatus",
|
"ShellStatus",
|
||||||
|
"carefully_setup_shell",
|
||||||
"error_from_execution_result",
|
"error_from_execution_result",
|
||||||
"event_from_execution_result",
|
"event_from_execution_result",
|
||||||
"event_from_history_output",
|
"event_from_history_output",
|
||||||
@@ -7,7 +7,7 @@ from time import monotonic
|
|||||||
from typing import Literal
|
from typing import Literal
|
||||||
from uuid import uuid4
|
from uuid import uuid4
|
||||||
|
|
||||||
from ipython_demo.utils import generate_good_names
|
from ipython_shell.utils import generate_good_names
|
||||||
|
|
||||||
from .models import AppInfo, CallOptions, CallResponse, ShellInfo
|
from .models import AppInfo, CallOptions, CallResponse, ShellInfo
|
||||||
from .shell import Shell
|
from .shell import Shell
|
||||||
@@ -0,0 +1,318 @@
|
|||||||
|
from collections import defaultdict
|
||||||
|
from datetime import UTC, datetime
|
||||||
|
from uuid import uuid4
|
||||||
|
|
||||||
|
from IPython.core.interactiveshell import InteractiveShell
|
||||||
|
from IPython.core.pylabtools import (
|
||||||
|
activate_matplotlib,
|
||||||
|
find_gui_and_backend,
|
||||||
|
import_pylab,
|
||||||
|
mpl_runner,
|
||||||
|
select_figure_formats,
|
||||||
|
)
|
||||||
|
from matplotlib_inline.backend_inline import configure_inline_support, flush_figures
|
||||||
|
|
||||||
|
from .events import (
|
||||||
|
error_from_execution_result,
|
||||||
|
event_from_execution_result,
|
||||||
|
event_from_history_output,
|
||||||
|
)
|
||||||
|
from .models import CallEvent, CallResult, ShellInfo, ShellStatus
|
||||||
|
|
||||||
|
|
||||||
|
class Shell:
|
||||||
|
"""Own one configured, in-process IPython shell."""
|
||||||
|
|
||||||
|
def __init__(self, name: str):
|
||||||
|
self.name = name
|
||||||
|
self.shell_id = uuid4().hex
|
||||||
|
self.created_at = datetime.now(UTC).isoformat()
|
||||||
|
self.last_used_at: str | None = None
|
||||||
|
self.status: ShellStatus = "ready"
|
||||||
|
self._last_execution_count = 0
|
||||||
|
self._shell: InteractiveShell | None = None
|
||||||
|
|
||||||
|
@property
|
||||||
|
def shell(self) -> InteractiveShell:
|
||||||
|
if self._shell is not None:
|
||||||
|
return self._shell
|
||||||
|
return self.init_shell()
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def using_shell(cls, shell: InteractiveShell, name: str) -> Shell:
|
||||||
|
"""Create a Shell object that wraps an existing InteractiveShell.
|
||||||
|
|
||||||
|
The caller owns setup for this low-level adapter.
|
||||||
|
"""
|
||||||
|
obj = cls(name)
|
||||||
|
obj._shell = shell
|
||||||
|
return obj
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def init_instance(cls, name: str = "default") -> Shell:
|
||||||
|
"""Create a Shell around IPython's process-wide instance.
|
||||||
|
|
||||||
|
``matplotlib-inline`` reaches the shell through IPython's global
|
||||||
|
``get_ipython()`` lookup. Wrapping ``InteractiveShell.instance()``
|
||||||
|
keeps those two identities aligned without changing the singleton's
|
||||||
|
existing configuration.
|
||||||
|
"""
|
||||||
|
return cls.using_shell(InteractiveShell.instance(), name)
|
||||||
|
|
||||||
|
def init_shell(self) -> InteractiveShell:
|
||||||
|
if self._shell is not None:
|
||||||
|
return self._shell
|
||||||
|
|
||||||
|
self._shell = InteractiveShell()
|
||||||
|
setup_shell(self._shell, plt=True, pylab=True)
|
||||||
|
return self._shell
|
||||||
|
|
||||||
|
def run_cell(
|
||||||
|
self,
|
||||||
|
code: str,
|
||||||
|
*,
|
||||||
|
call_id: str,
|
||||||
|
) -> tuple[CallResult, list[CallEvent]]:
|
||||||
|
"""Execute one cell and return its result plus ordered output events."""
|
||||||
|
self.status = "running"
|
||||||
|
try:
|
||||||
|
call_result, events = run_cell_and_collect(
|
||||||
|
self.shell,
|
||||||
|
code,
|
||||||
|
call_id=call_id,
|
||||||
|
)
|
||||||
|
except Exception:
|
||||||
|
self.status = "error"
|
||||||
|
raise
|
||||||
|
else:
|
||||||
|
self.status = "ready"
|
||||||
|
self.last_used_at = datetime.now(UTC).isoformat()
|
||||||
|
self._last_execution_count = call_result.execution_count or 0
|
||||||
|
return call_result, events
|
||||||
|
|
||||||
|
def describe(self) -> ShellInfo:
|
||||||
|
"""Return metadata without initializing an unused shell."""
|
||||||
|
return ShellInfo(
|
||||||
|
shell_id=self.shell_id,
|
||||||
|
name=self.name,
|
||||||
|
status=self.status,
|
||||||
|
execution_count=self._last_execution_count,
|
||||||
|
created_at=self.created_at,
|
||||||
|
last_used_at=self.last_used_at,
|
||||||
|
capabilities=(
|
||||||
|
"stdout",
|
||||||
|
"stderr",
|
||||||
|
"display_data",
|
||||||
|
"execute_result",
|
||||||
|
"error",
|
||||||
|
"matplotlib-inline",
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def isolate_output_history(shell: InteractiveShell) -> None:
|
||||||
|
"""Give this shell an instance-owned output ledger.
|
||||||
|
|
||||||
|
IPython declares ``HistoryManager.outputs`` on the class. Shadowing it
|
||||||
|
during shell setup prevents output records from leaking across shells.
|
||||||
|
"""
|
||||||
|
history_manager = shell.history_manager
|
||||||
|
if history_manager is None:
|
||||||
|
raise RuntimeError("InteractiveShell has no history manager")
|
||||||
|
history_manager.outputs = defaultdict(list)
|
||||||
|
|
||||||
|
|
||||||
|
def run_cell_and_collect(
|
||||||
|
shell: InteractiveShell,
|
||||||
|
code: str,
|
||||||
|
*,
|
||||||
|
call_id: str,
|
||||||
|
) -> tuple[CallResult, list[CallEvent]]:
|
||||||
|
"""Run one cell and snapshot its output records after execution."""
|
||||||
|
history_manager = shell.history_manager
|
||||||
|
if history_manager is None:
|
||||||
|
raise RuntimeError("InteractiveShell has no history manager")
|
||||||
|
if "outputs" not in history_manager.__dict__:
|
||||||
|
isolate_output_history(shell)
|
||||||
|
|
||||||
|
execution = shell.run_cell(code, store_history=True)
|
||||||
|
records = (
|
||||||
|
tuple(history_manager.outputs.get(execution.execution_count, ()))
|
||||||
|
if execution.execution_count is not None
|
||||||
|
else ()
|
||||||
|
)
|
||||||
|
events = [
|
||||||
|
event_from_history_output(
|
||||||
|
record,
|
||||||
|
call_id=call_id,
|
||||||
|
sequence=sequence,
|
||||||
|
)
|
||||||
|
for sequence, record in enumerate(records)
|
||||||
|
]
|
||||||
|
events = [event for event in events if event is not None]
|
||||||
|
|
||||||
|
error = error_from_execution_result(execution)
|
||||||
|
if error is not None:
|
||||||
|
error_event = event_from_execution_result(
|
||||||
|
execution,
|
||||||
|
call_id=call_id,
|
||||||
|
sequence=len(events),
|
||||||
|
)
|
||||||
|
if error_event is not None:
|
||||||
|
events.append(error_event)
|
||||||
|
|
||||||
|
return (
|
||||||
|
CallResult(
|
||||||
|
call_id=call_id,
|
||||||
|
execution_count=execution.execution_count,
|
||||||
|
result=execution.result,
|
||||||
|
error=error,
|
||||||
|
),
|
||||||
|
events,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def setup_shell(
|
||||||
|
shell: InteractiveShell,
|
||||||
|
*,
|
||||||
|
isolate_output_hist: bool = True,
|
||||||
|
plt: bool = False,
|
||||||
|
pylab: bool = True,
|
||||||
|
):
|
||||||
|
"""Configure an ``InteractiveShell`` without requiring GUI support.
|
||||||
|
|
||||||
|
``InteractiveShell.enable_matplotlib`` and ``enable_pylab`` eventually
|
||||||
|
call ``enable_gui``. The base shell intentionally does not implement that
|
||||||
|
method, so the lower-level matplotlib-inline setup is used here instead.
|
||||||
|
"""
|
||||||
|
setup_shell_history_disabled(shell)
|
||||||
|
|
||||||
|
# ``HistoryManager.outputs`` is class-level in the IPython version used
|
||||||
|
# here. Give every managed shell an instance-local mapping so execution
|
||||||
|
# records cannot leak between Shell objects.
|
||||||
|
if (
|
||||||
|
isolate_output_hist
|
||||||
|
and shell.history_manager is not None
|
||||||
|
and "outputs" not in shell.history_manager.__dict__
|
||||||
|
):
|
||||||
|
isolate_output_history(shell)
|
||||||
|
|
||||||
|
carefully_setup_shell(
|
||||||
|
shell,
|
||||||
|
activate=plt,
|
||||||
|
pylab=pylab,
|
||||||
|
install_runner=plt,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def setup_shell_history_disabled(shell: InteractiveShell):
|
||||||
|
"""Disable the shell's history manager to mostly disable writing to disk."""
|
||||||
|
if shell.history_manager is not None:
|
||||||
|
shell.history_manager.enabled = False
|
||||||
|
|
||||||
|
|
||||||
|
def carefully_setup_shell(
|
||||||
|
shell: InteractiveShell,
|
||||||
|
*,
|
||||||
|
gui: str = "inline",
|
||||||
|
activate: bool = True,
|
||||||
|
formats: set[str] | None = None,
|
||||||
|
pylab: bool = False,
|
||||||
|
import_all: bool = False,
|
||||||
|
install_runner: bool = False,
|
||||||
|
) -> tuple[str | None, str, list[str]]:
|
||||||
|
"""Add only the requested Matplotlib and pylab support to one shell.
|
||||||
|
|
||||||
|
This deliberately does not call ``enable_gui``. It performs the useful
|
||||||
|
parts of ``InteractiveShell.enable_matplotlib`` and ``enable_pylab`` for
|
||||||
|
shells whose base class has no GUI implementation.
|
||||||
|
"""
|
||||||
|
import matplotlib
|
||||||
|
|
||||||
|
selected_gui, backend = find_gui_and_backend(
|
||||||
|
gui,
|
||||||
|
shell.pylab_gui_select,
|
||||||
|
)
|
||||||
|
wanted_formats = formats or {"svg", "png", "pdf", "jpg"}
|
||||||
|
|
||||||
|
# select_figure_formats() clears Figure printers, so capture them before
|
||||||
|
# configuring inline support and restore them with our requested formats.
|
||||||
|
existing_formats = figure_formats_for(shell)
|
||||||
|
|
||||||
|
if activate and matplotlib.get_backend().lower() != backend.lower():
|
||||||
|
activate_matplotlib(backend)
|
||||||
|
|
||||||
|
if not inline_support_present(shell, backend):
|
||||||
|
configure_inline_support(shell, backend)
|
||||||
|
|
||||||
|
select_figure_formats(shell, existing_formats | wanted_formats)
|
||||||
|
|
||||||
|
if install_runner:
|
||||||
|
execution_magics = shell.magics_manager.registry["ExecutionMagics"]
|
||||||
|
if execution_magics.default_runner is None:
|
||||||
|
execution_magics.default_runner = mpl_runner(shell.safe_execfile)
|
||||||
|
|
||||||
|
clobbered = (
|
||||||
|
enable_pylab_lite(shell, import_all=import_all) if pylab else []
|
||||||
|
)
|
||||||
|
return selected_gui, backend, clobbered
|
||||||
|
|
||||||
|
|
||||||
|
def inline_support_present(shell: InteractiveShell, backend: str) -> bool:
|
||||||
|
"""Return whether this shell has the requested inline flush support."""
|
||||||
|
callbacks = shell.events.callbacks.get("post_execute", [])
|
||||||
|
is_inline = backend.lower() in {
|
||||||
|
"inline",
|
||||||
|
"module://matplotlib_inline.backend_inline",
|
||||||
|
}
|
||||||
|
return (flush_figures in callbacks) if is_inline else (flush_figures not in callbacks)
|
||||||
|
|
||||||
|
|
||||||
|
def figure_formats_for(shell: InteractiveShell) -> set[str]:
|
||||||
|
"""Return MIME figure formats currently registered on one shell."""
|
||||||
|
from matplotlib.figure import Figure
|
||||||
|
|
||||||
|
mime_to_format = {
|
||||||
|
"image/png": "png",
|
||||||
|
"image/jpeg": "jpg",
|
||||||
|
"image/svg+xml": "svg",
|
||||||
|
"application/pdf": "pdf",
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
format_name
|
||||||
|
for mime, format_name in mime_to_format.items()
|
||||||
|
if Figure in shell.display_formatter.formatters[mime].type_printers
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def enable_pylab_lite(
|
||||||
|
shell: InteractiveShell,
|
||||||
|
*,
|
||||||
|
import_all: bool = True,
|
||||||
|
) -> list[str]:
|
||||||
|
"""Load pylab names without GUI activation and report overwritten names."""
|
||||||
|
pylab_ns: dict[str, object] = {}
|
||||||
|
import_pylab(pylab_ns, import_all)
|
||||||
|
|
||||||
|
ignored = {"__builtins__"}
|
||||||
|
candidates = set(pylab_ns).intersection(shell.user_ns).difference(ignored)
|
||||||
|
clobbered = [
|
||||||
|
name
|
||||||
|
for name in candidates
|
||||||
|
if shell.user_ns[name] is not pylab_ns[name]
|
||||||
|
]
|
||||||
|
|
||||||
|
shell.user_ns.update(pylab_ns)
|
||||||
|
shell.user_ns_hidden.update(pylab_ns)
|
||||||
|
return clobbered
|
||||||
|
|
||||||
|
|
||||||
|
_default: Shell | None = None
|
||||||
|
|
||||||
|
|
||||||
|
def get_default_shell() -> Shell:
|
||||||
|
"""Return the lazily-created, configured process-wide shell."""
|
||||||
|
global _default
|
||||||
|
if _default is None:
|
||||||
|
_default = Shell.init_instance("default")
|
||||||
|
return _default
|
||||||
@@ -2,8 +2,8 @@ from typing import Annotated, Literal
|
|||||||
|
|
||||||
from fastapi import Body, Depends, FastAPI, HTTPException, Path, Query
|
from fastapi import Body, Depends, FastAPI, HTTPException, Path, Query
|
||||||
|
|
||||||
from ipython_demo import App
|
from ipython_shell import App
|
||||||
from ipython_demo.models import CallOptions
|
from ipython_shell.models import CallOptions
|
||||||
|
|
||||||
app = FastAPI(title="IPython Demo", version="0.1.0")
|
app = FastAPI(title="IPython Demo", version="0.1.0")
|
||||||
|
|
||||||
|
|||||||
@@ -2,8 +2,8 @@ from typing import Literal
|
|||||||
|
|
||||||
from langchain.tools import tool
|
from langchain.tools import tool
|
||||||
|
|
||||||
from ipython_demo import App
|
from ipython_shell import App
|
||||||
from ipython_demo.models import AppInfo, CallOptions, CallResponse, ShellInfo
|
from ipython_shell.models import AppInfo, CallOptions, CallResponse, ShellInfo
|
||||||
|
|
||||||
app = App() # global, again
|
app = App() # global, again
|
||||||
|
|
||||||
|
|||||||
+3
-3
@@ -4,9 +4,9 @@ import re
|
|||||||
import unittest
|
import unittest
|
||||||
from unittest.mock import Mock, patch
|
from unittest.mock import Mock, patch
|
||||||
|
|
||||||
from ipython_demo import utils
|
from ipython_shell import utils
|
||||||
from ipython_demo.app import App
|
from ipython_shell.app import App
|
||||||
from ipython_demo.models import CallOptions, CallResponse
|
from ipython_shell.models import CallOptions, CallResponse
|
||||||
|
|
||||||
|
|
||||||
class AppTests(unittest.TestCase):
|
class AppTests(unittest.TestCase):
|
||||||
|
|||||||
+1
-1
@@ -2,7 +2,7 @@ import unittest
|
|||||||
|
|
||||||
from fastapi.testclient import TestClient
|
from fastapi.testclient import TestClient
|
||||||
|
|
||||||
from ipython_demo import App, AppInfo
|
from ipython_shell import App, AppInfo
|
||||||
from ipython_mcp.app import app as mcp_app
|
from ipython_mcp.app import app as mcp_app
|
||||||
from ipython_webapp.app import app as web_app
|
from ipython_webapp.app import app as web_app
|
||||||
from langchain_demo.ipython_wrapper import get_app_info
|
from langchain_demo.ipython_wrapper import get_app_info
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ from typing import Any
|
|||||||
|
|
||||||
from IPython.core.interactiveshell import InteractiveShell
|
from IPython.core.interactiveshell import InteractiveShell
|
||||||
|
|
||||||
from ipython_demo.shell import Shell
|
from ipython_shell.shell import Shell
|
||||||
|
|
||||||
|
|
||||||
class TestMyIdeas(unittest.TestCase):
|
class TestMyIdeas(unittest.TestCase):
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import unittest
|
|||||||
from fastapi.encoders import jsonable_encoder
|
from fastapi.encoders import jsonable_encoder
|
||||||
from fastapi.testclient import TestClient
|
from fastapi.testclient import TestClient
|
||||||
|
|
||||||
from ipython_demo import App, Shell
|
from ipython_shell import App, Shell
|
||||||
from ipython_webapp.app import app as web_app
|
from ipython_webapp.app import app as web_app
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
+34
-3
@@ -8,9 +8,9 @@ from IPython.core.history import HistoryOutput
|
|||||||
from IPython.core.interactiveshell import InteractiveShell
|
from IPython.core.interactiveshell import InteractiveShell
|
||||||
from IPython.utils import capture
|
from IPython.utils import capture
|
||||||
|
|
||||||
import ipython_demo.events as events_module
|
import ipython_shell.events as events_module
|
||||||
import ipython_demo.models as models_module
|
import ipython_shell.models as models_module
|
||||||
import ipython_demo.shell as shell_module
|
import ipython_shell.shell as shell_module
|
||||||
|
|
||||||
PACKAGE_ROOT = Path(__file__).parents[1] / "src" / "ipython_demo"
|
PACKAGE_ROOT = Path(__file__).parents[1] / "src" / "ipython_demo"
|
||||||
|
|
||||||
@@ -123,6 +123,37 @@ class ShellTests(unittest.TestCase):
|
|||||||
shell.magics_manager.registry["ExecutionMagics"].default_runner
|
shell.magics_manager.registry["ExecutionMagics"].default_runner
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def test_carefully_setup_shell_preserves_formats_and_is_idempotent(self):
|
||||||
|
from matplotlib.figure import Figure
|
||||||
|
from matplotlib_inline.backend_inline import flush_figures
|
||||||
|
|
||||||
|
shell = InteractiveShell()
|
||||||
|
|
||||||
|
shell_module.carefully_setup_shell(shell, formats={"svg"})
|
||||||
|
shell_module.carefully_setup_shell(shell, formats={"png"})
|
||||||
|
|
||||||
|
callbacks = shell.events.callbacks["post_execute"]
|
||||||
|
self.assertEqual(callbacks.count(flush_figures), 1)
|
||||||
|
self.assertIn(
|
||||||
|
Figure,
|
||||||
|
shell.display_formatter.formatters["image/svg+xml"].type_printers,
|
||||||
|
)
|
||||||
|
self.assertIn(
|
||||||
|
Figure,
|
||||||
|
shell.display_formatter.formatters["image/png"].type_printers,
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_enable_pylab_lite_reports_clobbered_names(self):
|
||||||
|
shell = InteractiveShell()
|
||||||
|
original_np = object()
|
||||||
|
shell.user_ns["np"] = original_np
|
||||||
|
|
||||||
|
clobbered = shell_module.enable_pylab_lite(shell, import_all=False)
|
||||||
|
|
||||||
|
self.assertIn("np", clobbered)
|
||||||
|
self.assertIsNot(shell.user_ns["np"], original_np)
|
||||||
|
self.assertIs(shell.user_ns["np"], shell.user_ns_hidden["np"])
|
||||||
|
|
||||||
def test_run_cell_does_not_use_global_capture_output(self):
|
def test_run_cell_does_not_use_global_capture_output(self):
|
||||||
wrapper = shell_module.Shell("test")
|
wrapper = shell_module.Shell("test")
|
||||||
wrapper.init_shell()
|
wrapper.init_shell()
|
||||||
|
|||||||
Reference in New Issue
Block a user