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]
|
||||
ipython-demo = "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-mcp = "ipython_mcp.app:main"
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@ from collections.abc import AsyncIterator, Awaitable, Callable
|
||||
from dataclasses import dataclass
|
||||
from typing import Any
|
||||
|
||||
from jupyter_client import AsyncKernelManager
|
||||
from jupyter_client.manager import AsyncKernelManager
|
||||
|
||||
|
||||
@dataclass
|
||||
@@ -34,9 +34,9 @@ class JupyterTransport:
|
||||
self._active_call: str | None = None
|
||||
self._call_lock = asyncio.Lock()
|
||||
self._waiting_for_input: str | None = None
|
||||
self._message_queue: asyncio.Queue[
|
||||
tuple[str, dict[str, Any] | BaseException]
|
||||
] | None = None
|
||||
self._message_queue: (
|
||||
asyncio.Queue[tuple[str, dict[str, Any] | BaseException]] | None
|
||||
) = None
|
||||
self._reader_tasks: set[asyncio.Task[None]] = set()
|
||||
|
||||
async def start(self) -> None:
|
||||
@@ -68,7 +68,7 @@ class JupyterTransport:
|
||||
async def _read_channel(
|
||||
self,
|
||||
channel: str,
|
||||
get_message: Callable[[], Awaitable[dict[str, Any]]],
|
||||
get_message: Callable[..., Awaitable[dict[str, Any]]],
|
||||
) -> None:
|
||||
"""Read one ZMQ channel continuously into the transport queue."""
|
||||
if self._message_queue is None:
|
||||
@@ -143,9 +143,7 @@ class JupyterTransport:
|
||||
msg_type=message["msg_type"],
|
||||
parent_id=parent_id,
|
||||
content=dict(message.get("content", {})),
|
||||
buffers=[
|
||||
bytes(buffer) for buffer in message.get("buffers", [])
|
||||
],
|
||||
buffers=[bytes(buffer) for buffer in message.get("buffers", [])],
|
||||
)
|
||||
if decoded.msg_type == "execute_reply":
|
||||
# The shell reply and IOPub messages use different
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
import inspect
|
||||
import secrets
|
||||
from collections.abc import Sequence
|
||||
from typing import Literal
|
||||
from uuid import uuid4
|
||||
|
||||
from wonderwords import RandomWord
|
||||
@@ -22,3 +25,21 @@ def generate_good_names() -> str:
|
||||
adjective = _WORD_GENERATOR.word(include_categories=["adjectives"])
|
||||
noun = _WORD_GENERATOR.word(include_categories=["nouns"])
|
||||
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(
|
||||
shell_name: Annotated[
|
||||
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[
|
||||
str,
|
||||
|
||||
@@ -29,9 +29,7 @@ class AsyncTransportTests(unittest.IsolatedAsyncioTestCase):
|
||||
try:
|
||||
readers = set(transport._reader_tasks)
|
||||
call_id = await transport.execute("2 + 2")
|
||||
messages = [
|
||||
message async for message in transport.messages_for(call_id)
|
||||
]
|
||||
messages = [message async for message in transport.messages_for(call_id)]
|
||||
|
||||
self.assertEqual(len(readers), 3)
|
||||
self.assertEqual(transport._reader_tasks, readers)
|
||||
|
||||
Reference in New Issue
Block a user