stuff, fmt

This commit is contained in:
lda
2026-08-30 04:30:06 +07:00 Verified
parent 282dc5b4e8
commit b4e5058000
7 changed files with 81 additions and 94 deletions
+7 -6
View File
@@ -1,6 +1,6 @@
"""Small, side-effect-free API for the in-process IPython runner.""" """Small, side-effect-free API for the in-process IPython runner."""
from .app import App, generate_good_names from .app import App
from .events import ( from .events import (
error_from_execution_result, error_from_execution_result,
event_from_execution_result, event_from_execution_result,
@@ -22,22 +22,23 @@ from .shell import (
run_cell_and_collect, run_cell_and_collect,
setup_shell, setup_shell,
) )
from .utils import generate_good_names
__all__ = [ __all__ = [
"App", "App",
"CallError",
"CallEvent", "CallEvent",
"CallEventKind", "CallEventKind",
"CallError",
"CallOptions", "CallOptions",
"CallResponse", "CallResponse",
"CallResult", "CallResult",
"error_from_execution_result",
"event_from_execution_result",
"Shell", "Shell",
"event_from_history_output",
"generate_good_names",
"ShellInfo", "ShellInfo",
"ShellStatus", "ShellStatus",
"error_from_execution_result",
"event_from_execution_result",
"event_from_history_output",
"generate_good_names",
"isolate_output_history", "isolate_output_history",
"run_cell_and_collect", "run_cell_and_collect",
"setup_shell", "setup_shell",
+1 -11
View File
@@ -4,22 +4,12 @@ import json
from dataclasses import asdict from dataclasses import asdict
from uuid import uuid4 from uuid import uuid4
from wonderwords import RandomWord from ipython_demo.utils import generate_good_names
from .models import CallOptions, CallResponse, ShellInfo from .models import CallOptions, CallResponse, ShellInfo
from .shell import Shell from .shell import Shell
_WORD_GENERATOR = RandomWord()
def generate_good_names() -> str:
"""Generate a readable, collision-resistant shell name."""
adjective = _WORD_GENERATOR.word(include_parts_of_speech=["adjectives"])
noun = _WORD_GENERATOR.word(include_parts_of_speech=["nouns"])
return f"{adjective}-{noun}-{uuid4().hex[:4]}"
class App: class App:
"""Coordinate named shells and expose frontend-shaped call responses.""" """Coordinate named shells and expose frontend-shaped call responses."""
-1
View File
@@ -1,7 +1,6 @@
from dataclasses import dataclass from dataclasses import dataclass
from typing import Literal from typing import Literal
CallEventKind = Literal[ CallEventKind = Literal[
"stdout", "stdout",
"stderr", "stderr",
+13 -1
View File
@@ -1,8 +1,10 @@
import inspect import inspect
from uuid import uuid4
from wonderwords import RandomWord
def line(): def line():
this = inspect.currentframe() this = inspect.currentframe()
if not this: # i want ?. operator SOOOOOOOO if not this: # i want ?. operator SOOOOOOOO
return None return None
@@ -10,3 +12,13 @@ def line():
if not frame: if not frame:
return None return None
return frame.f_lineno return frame.f_lineno
_WORD_GENERATOR = RandomWord()
def generate_good_names() -> str:
"""Generate a readable, collision-resistant shell name."""
adjective = _WORD_GENERATOR.word(include_categories=["adjectives"])
noun = _WORD_GENERATOR.word(include_categories=["nouns"])
return f"{adjective}-{noun}-{uuid4().hex[:4]}"
+6 -22
View File
@@ -1,47 +1,31 @@
import contextlib import contextlib
import importlib
import io import io
import json
import re import re
import subprocess
import sys
import unittest import unittest
from unittest.mock import Mock, patch from unittest.mock import Mock, patch
from ipython_demo import utils
from ipython_demo.app import App from ipython_demo.app import App
from ipython_demo.models import CallOptions, CallResponse from ipython_demo.models import CallOptions, CallResponse
class AppTests(unittest.TestCase): class AppTests(unittest.TestCase):
def test_generate_good_names_uses_readable_random_words(self): def test_generate_good_names_uses_readable_random_words(self):
app_module = importlib.import_module("ipython_demo.app")
generator = Mock() generator = Mock()
assert isinstance(generator.word, Mock)
generator.word.side_effect = ["luminous", "otter"] generator.word.side_effect = ["luminous", "otter"]
with patch.object( with patch.object(
app_module, utils,
"_WORD_GENERATOR", "_WORD_GENERATOR",
generator, generator,
create=True, create=True,
): ):
name = app_module.generate_good_names() name = utils.generate_good_names()
self.assertRegex(name, re.compile(r"^[a-z]+-[a-z]+-[0-9a-f]{4}$")) self.assertRegex(name, re.compile(r"^[a-z]+-[a-z]+-[0-9a-f]{4}$"))
generator.word.assert_any_call(include_parts_of_speech=["adjectives"]) generator.word.assert_any_call(include_categories=["adjectives"])
generator.word.assert_any_call(include_parts_of_speech=["nouns"]) generator.word.assert_any_call(include_categories=["nouns"])
def test_main_emits_one_json_payload(self):
completed = subprocess.run(
[sys.executable, "-c", "from ipython_demo.app import main; main()"],
capture_output=True,
text=True,
check=True,
)
payload = json.loads(completed.stdout)
self.assertEqual(len(payload), 2)
self.assertEqual(payload[0]["events"][0]["kind"], "stdout")
self.assertTrue(payload[1]["options"]["collapsed"])
def test_run_code_returns_frontend_facing_call_response(self): def test_run_code_returns_frontend_facing_call_response(self):
app = App() app = App()
+35
View File
@@ -0,0 +1,35 @@
import unittest
from collections.abc import Callable
from typing import Any
from IPython.core.interactiveshell import InteractiveShell
from ipython_demo.shell import Shell
class TestMyIdeas(unittest.TestCase):
def test_MY_two_instances(self):
InteractiveShell1 = Shell("InteractiveShell1").init_shell()
InteractiveShell2 = Shell("InteractiveShell2").init_shell()
def ok[T](
resolve: Callable[[InteractiveShell], Any],
method: Callable[[T, T], Any],
) -> None:
t1 = resolve(InteractiveShell1)
t2 = resolve(InteractiveShell2)
method(t1, t2)
ok(
lambda shell: getattr(shell.history_manager, "outputs", None),
self.assertIsNot,
)
ok(lambda shell: shell.display_formatter, self.assertIsNot)
ok(lambda shell: shell.display_pub, self.assertIsNot)
ok(lambda shell: shell.displayhook, self.assertIsNot)
ok(lambda shell: shell.history_manager, self.assertIsNot)
ok(lambda shell: shell.user_ns, self.assertIsNot)
if __name__ == "__main__":
unittest.main()
+12 -46
View File
@@ -1,61 +1,21 @@
import contextlib import contextlib
import importlib.util
import io import io
import os
import subprocess
import sys
import types
import unittest import unittest
from pathlib import Path from pathlib import Path
from unittest.mock import patch from unittest.mock import patch
from IPython.core.interactiveshell import InteractiveShell
from IPython.core.history import HistoryOutput from IPython.core.history import HistoryOutput
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_demo.models as models_module
import ipython_demo.shell as shell_module
PACKAGE_ROOT = Path(__file__).parents[1] / "src" / "ipython_demo" PACKAGE_ROOT = Path(__file__).parents[1] / "src" / "ipython_demo"
package = types.ModuleType("ipython_demo")
package.__path__ = [str(PACKAGE_ROOT)]
sys.modules["ipython_demo"] = package
def load_package_module(name: str):
path = PACKAGE_ROOT / f"{name}.py"
spec = importlib.util.spec_from_file_location(f"ipython_demo.{name}", path)
assert spec is not None and spec.loader is not None
module = importlib.util.module_from_spec(spec)
sys.modules[spec.name] = module
spec.loader.exec_module(module)
return module
models_module = load_package_module("models")
events_module = load_package_module("events")
shell_module = load_package_module("shell")
class ShellTests(unittest.TestCase): class ShellTests(unittest.TestCase):
def test_init_shell_has_no_seed_escape_hatch(self):
import inspect
self.assertNotIn(
"seed", inspect.signature(shell_module.Shell.init_shell).parameters
)
def test_package_import_has_no_experiment_side_effects(self):
env = os.environ.copy()
env["PYTHONPATH"] = str(PACKAGE_ROOT.parent)
completed = subprocess.run(
[sys.executable, "-c", "import ipython_demo; print('imported')"],
capture_output=True,
text=True,
env=env,
check=True,
)
self.assertEqual(completed.stdout, "imported\n")
def test_event_from_history_output_preserves_streams_and_mime(self): def test_event_from_history_output_preserves_streams_and_mime(self):
stdout = events_module.event_from_history_output( stdout = events_module.event_from_history_output(
HistoryOutput("out_stream", {"stream": ["hello", "\n"]}), HistoryOutput("out_stream", {"stream": ["hello", "\n"]}),
@@ -105,9 +65,13 @@ class ShellTests(unittest.TestCase):
) )
self.assertIsInstance(execution.error, models_module.CallError) self.assertIsInstance(execution.error, models_module.CallError)
assert execution.error
self.assertEqual(execution.error.ename, "ValueError") self.assertEqual(execution.error.ename, "ValueError")
self.assertEqual(execution.error.evalue, "boom") self.assertEqual(execution.error.evalue, "boom")
self.assertTrue(events, "no events")
self.assertEqual(events[-1].kind, "error") self.assertEqual(events[-1].kind, "error")
self.assertTrue(events[-1].data, "no data in last event")
assert events[-1].data
self.assertEqual(events[-1].data["ename"], "ValueError") self.assertEqual(events[-1].data["ename"], "ValueError")
self.assertEqual(events[-1].data["evalue"], "boom") self.assertEqual(events[-1].data["evalue"], "boom")
@@ -168,12 +132,14 @@ class ShellTests(unittest.TestCase):
"Shell.run_cell must use the shell-local output records" "Shell.run_cell must use the shell-local output records"
) )
with patch.object( with (
patch.object(
capture, capture,
"capture_output", "capture_output",
fail_capture_output, fail_capture_output,
),
contextlib.redirect_stdout(io.StringIO()),
): ):
with contextlib.redirect_stdout(io.StringIO()):
result, events = wrapper.run_cell( result, events = wrapper.run_cell(
"print('hello'); 42", "print('hello'); 42",
call_id="call-1", call_id="call-1",