fmt, stuff

This commit is contained in:
lda
2026-08-30 22:31:12 +07:00 Verified
parent e2958ea91a
commit 0b87620852
7 changed files with 87 additions and 13 deletions
+29
View File
@@ -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
View File
@@ -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"
+6 -8
View File
@@ -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
+21
View File
@@ -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)
+25
View File
@@ -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()
+4 -1
View File
@@ -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,
+1 -3
View File
@@ -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)