fmt, stuff
This commit is contained in:
+29
@@ -0,0 +1,29 @@
|
|||||||
|
# IPython Shell Execution
|
||||||
|
|
||||||
|
This project exposes persistent Python environments through web and model-facing adapters. The glossary keeps the long-lived execution environment distinct from each individual code execution.
|
||||||
|
|
||||||
|
## Execution
|
||||||
|
|
||||||
|
**Shell**:
|
||||||
|
A persistent, isolated Python environment that retains its namespace and execution state across calls.
|
||||||
|
_Avoid_: Session, request, call
|
||||||
|
|
||||||
|
**Call**:
|
||||||
|
One request to execute code in one shell. A call has its own identity and lifecycle, can emit ordered events, can pause for input, and eventually produces a result or error.
|
||||||
|
_Avoid_: Shell, session, task
|
||||||
|
|
||||||
|
**Call ID**:
|
||||||
|
The application-facing identity of one call, used to associate its events, input requests, and final result. Any execution ID used internally by a transport is not part of this identity.
|
||||||
|
_Avoid_: Execution count, kernel ID
|
||||||
|
|
||||||
|
**Call Event**:
|
||||||
|
An ordered observable part of a call, such as standard output, standard error, display data, an input request, or an execution error.
|
||||||
|
_Avoid_: Log line, transport message
|
||||||
|
|
||||||
|
**App**:
|
||||||
|
The owner of the available shells and calls. It resolves shell selectors such as `new`, `last`, or a named shell and routes each call to its target shell.
|
||||||
|
_Avoid_: Server, session
|
||||||
|
|
||||||
|
**Shell Name**:
|
||||||
|
A user-facing selector for a shell. `new` requests a fresh shell, `last` selects the most recently selected shell, and any other name identifies a reusable named shell.
|
||||||
|
_Avoid_: Shell ID, kernel name
|
||||||
+1
-1
@@ -22,7 +22,7 @@ dependencies = [
|
|||||||
[project.scripts]
|
[project.scripts]
|
||||||
ipython-demo = "ipython_shell.app:main"
|
ipython-demo = "ipython_shell.app:main"
|
||||||
ipython-shell = "ipython_shell.app:main"
|
ipython-shell = "ipython_shell.app:main"
|
||||||
ipython-webapp = "ipython_webapp.app:main"
|
ipython-webapp = "ipython_webapp:main"
|
||||||
ipython-jupyter-webapp = "ipython_webapp.jupyter.app:main"
|
ipython-jupyter-webapp = "ipython_webapp.jupyter.app:main"
|
||||||
ipython-mcp = "ipython_mcp.app:main"
|
ipython-mcp = "ipython_mcp.app:main"
|
||||||
|
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ from collections.abc import AsyncIterator, Awaitable, Callable
|
|||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
from jupyter_client import AsyncKernelManager
|
from jupyter_client.manager import AsyncKernelManager
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
@@ -34,9 +34,9 @@ class JupyterTransport:
|
|||||||
self._active_call: str | None = None
|
self._active_call: str | None = None
|
||||||
self._call_lock = asyncio.Lock()
|
self._call_lock = asyncio.Lock()
|
||||||
self._waiting_for_input: str | None = None
|
self._waiting_for_input: str | None = None
|
||||||
self._message_queue: asyncio.Queue[
|
self._message_queue: (
|
||||||
tuple[str, dict[str, Any] | BaseException]
|
asyncio.Queue[tuple[str, dict[str, Any] | BaseException]] | None
|
||||||
] | None = None
|
) = None
|
||||||
self._reader_tasks: set[asyncio.Task[None]] = set()
|
self._reader_tasks: set[asyncio.Task[None]] = set()
|
||||||
|
|
||||||
async def start(self) -> None:
|
async def start(self) -> None:
|
||||||
@@ -68,7 +68,7 @@ class JupyterTransport:
|
|||||||
async def _read_channel(
|
async def _read_channel(
|
||||||
self,
|
self,
|
||||||
channel: str,
|
channel: str,
|
||||||
get_message: Callable[[], Awaitable[dict[str, Any]]],
|
get_message: Callable[..., Awaitable[dict[str, Any]]],
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Read one ZMQ channel continuously into the transport queue."""
|
"""Read one ZMQ channel continuously into the transport queue."""
|
||||||
if self._message_queue is None:
|
if self._message_queue is None:
|
||||||
@@ -143,9 +143,7 @@ class JupyterTransport:
|
|||||||
msg_type=message["msg_type"],
|
msg_type=message["msg_type"],
|
||||||
parent_id=parent_id,
|
parent_id=parent_id,
|
||||||
content=dict(message.get("content", {})),
|
content=dict(message.get("content", {})),
|
||||||
buffers=[
|
buffers=[bytes(buffer) for buffer in message.get("buffers", [])],
|
||||||
bytes(buffer) for buffer in message.get("buffers", [])
|
|
||||||
],
|
|
||||||
)
|
)
|
||||||
if decoded.msg_type == "execute_reply":
|
if decoded.msg_type == "execute_reply":
|
||||||
# The shell reply and IOPub messages use different
|
# The shell reply and IOPub messages use different
|
||||||
|
|||||||
@@ -1,4 +1,7 @@
|
|||||||
import inspect
|
import inspect
|
||||||
|
import secrets
|
||||||
|
from collections.abc import Sequence
|
||||||
|
from typing import Literal
|
||||||
from uuid import uuid4
|
from uuid import uuid4
|
||||||
|
|
||||||
from wonderwords import RandomWord
|
from wonderwords import RandomWord
|
||||||
@@ -22,3 +25,21 @@ def generate_good_names() -> str:
|
|||||||
adjective = _WORD_GENERATOR.word(include_categories=["adjectives"])
|
adjective = _WORD_GENERATOR.word(include_categories=["adjectives"])
|
||||||
noun = _WORD_GENERATOR.word(include_categories=["nouns"])
|
noun = _WORD_GENERATOR.word(include_categories=["nouns"])
|
||||||
return f"{adjective}-{noun}-{uuid4().hex[:4]}"
|
return f"{adjective}-{noun}-{uuid4().hex[:4]}"
|
||||||
|
|
||||||
|
|
||||||
|
type WordCategory = Literal["adjectives", "nouns", "verbs"]
|
||||||
|
type WordCategory1 = Literal["adjective", "noun", "verb"]
|
||||||
|
|
||||||
|
|
||||||
|
def generate_omfg_names(uhh: Sequence[WordCategory | WordCategory1 | int]) -> str:
|
||||||
|
"""Generate a readable, collision-resistant shell name.
|
||||||
|
|
||||||
|
Example: generate_omfg_names(["adjectives", "nouns", 4]) -> "happy-dog-1a2b"
|
||||||
|
"""
|
||||||
|
words: list[str] = []
|
||||||
|
for category in uhh:
|
||||||
|
if isinstance(category, int):
|
||||||
|
words.append(secrets.token_hex((category + 1) // 2)[:category]) # what?
|
||||||
|
else:
|
||||||
|
words.append(_WORD_GENERATOR.word(include_categories=[category]))
|
||||||
|
return "-".join(words)
|
||||||
|
|||||||
@@ -0,0 +1,25 @@
|
|||||||
|
import argparse
|
||||||
|
|
||||||
|
|
||||||
|
def main() -> None:
|
||||||
|
from .app import app as interactive_shell_app
|
||||||
|
from .jupyter import app as jupyter_app
|
||||||
|
|
||||||
|
parser = argparse.ArgumentParser(description="Run the Jupyter shell web app.")
|
||||||
|
|
||||||
|
parser.add_argument(
|
||||||
|
"--jupyter",
|
||||||
|
action="store_true",
|
||||||
|
help="Run the Jupyter shell web app.",
|
||||||
|
)
|
||||||
|
args = parser.parse_args()
|
||||||
|
import uvicorn
|
||||||
|
|
||||||
|
if args.jupyter:
|
||||||
|
uvicorn.run(jupyter_app, host="::", port=8000, log_level="info")
|
||||||
|
else:
|
||||||
|
uvicorn.run(interactive_shell_app, host="::", port=8000, log_level="info")
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
|
|||||||
@@ -52,7 +52,10 @@ async def _stream_events(code: str, shell_name: str) -> AsyncIterator[str]:
|
|||||||
async def run_code(
|
async def run_code(
|
||||||
shell_name: Annotated[
|
shell_name: Annotated[
|
||||||
str,
|
str,
|
||||||
Path(..., description="The Jupyter shell to run code in"),
|
Path(
|
||||||
|
...,
|
||||||
|
description="The Jupyter shell to run code in. Existing shell guess. 'new' creates a new shell, 'last' uses the most recent shell.",
|
||||||
|
),
|
||||||
],
|
],
|
||||||
code: Annotated[
|
code: Annotated[
|
||||||
str,
|
str,
|
||||||
|
|||||||
@@ -29,9 +29,7 @@ class AsyncTransportTests(unittest.IsolatedAsyncioTestCase):
|
|||||||
try:
|
try:
|
||||||
readers = set(transport._reader_tasks)
|
readers = set(transport._reader_tasks)
|
||||||
call_id = await transport.execute("2 + 2")
|
call_id = await transport.execute("2 + 2")
|
||||||
messages = [
|
messages = [message async for message in transport.messages_for(call_id)]
|
||||||
message async for message in transport.messages_for(call_id)
|
|
||||||
]
|
|
||||||
|
|
||||||
self.assertEqual(len(readers), 3)
|
self.assertEqual(len(readers), 3)
|
||||||
self.assertEqual(transport._reader_tasks, readers)
|
self.assertEqual(transport._reader_tasks, readers)
|
||||||
|
|||||||
Reference in New Issue
Block a user