kernmini 0.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
kernmini/__init__.py ADDED
@@ -0,0 +1,17 @@
1
+ """Everything a Jupyter kernel needs except the language.
2
+
3
+ kernmini is the language-agnostic core of a Jupyter kernel: connection files, HMAC-signed wire
4
+ sessions, the socket thread cast (shell/control routers, IOPub, stdin, heartbeat), busy/idle and
5
+ abort discipline, interrupts, subshells (JEP 91), and process lifecycle. A kernel supplies a
6
+ *shell*: an execution layer built by the `shell_factory` passed to `MiniKernel`/`run_kernel`.
7
+ See DEV.md for the shell contract; `ipymini` is the reference implementation (IPython), and a
8
+ minimal shell needs only `execute`, `execution_count`, `execution_context`, and
9
+ `set_stream_sender`.
10
+ """
11
+
12
+ __version__ = "0.1.0"
13
+
14
+ from .kernel import MiniKernel, run_kernel
15
+ from .session import MiniSession
16
+ from .concur import unlock, subshell
17
+ from .kernelspec import install_kernelspec, install_kernelspec_dir
kernmini/concur.py ADDED
@@ -0,0 +1,30 @@
1
+ "In-cell opt-ins for concurrent execution: unlock() and subshell()."
2
+
3
+ import contextvars
4
+ from contextlib import contextmanager
5
+
6
+ _release = contextvars.ContextVar("kernmini_release", default=None)
7
+ _subshell = contextvars.ContextVar("kernmini_subshell", default=None)
8
+
9
+
10
+ def unlock()->bool:
11
+ "Let queued shell messages run while the current cell awaits; irreversible for the rest of the cell."
12
+ release = _release.get()
13
+ if release is None: return False
14
+ release()
15
+ return True
16
+
17
+
18
+ @contextmanager
19
+ def subshell():
20
+ "Run execute_requests arriving from this cell's client session in a fresh subshell while the body runs."
21
+ sub = _subshell.get()
22
+ if sub is None: raise RuntimeError("subshell() only works inside a cell running under a kernmini kernel")
23
+ subs = sub.kernel.subshells
24
+ session = ((sub.kernel.current_parent() or {}).get("header") or {}).get("session")
25
+ sid = subs.create()
26
+ try:
27
+ subs.set_route_override(sid, session)
28
+ try: yield sid
29
+ finally: subs.clear_route_override()
30
+ finally: subs.delete(sid)
kernmini/debug.py ADDED
@@ -0,0 +1,81 @@
1
+ "Debug infrastructure for kernels: env flags, logging/faulthandler setup, message tracing, and debugger cell-filename hashing."
2
+
3
+ import faulthandler, logging, os, signal, sys, tempfile
4
+ from dataclasses import dataclass
5
+
6
+
7
+ def envbool(name: str) -> bool:
8
+ v = (os.environ.get(name) or "").strip().lower()
9
+ return v not in ("", "0", "false", "no")
10
+
11
+
12
+ @dataclass(frozen=True)
13
+ class DebugFlags:
14
+ enabled: bool = False
15
+ trace_msgs: bool = False
16
+
17
+ @classmethod
18
+ def from_env(cls, prefix: str = "KERNMINI") -> "DebugFlags": return cls(enabled=envbool(f"{prefix}_DEBUG"), trace_msgs=envbool(f"{prefix}_DEBUG_MSGS"))
19
+
20
+
21
+ def setup_debug(flags: DebugFlags):
22
+ "Initialize debug infrastructure: logging, faulthandler, SIGUSR1 handler."
23
+ if not flags.enabled: return
24
+ root = logging.getLogger()
25
+ fmt = "%(asctime)s %(levelname)s %(name)s: %(message)s"
26
+ if not root.handlers: logging.basicConfig(level=logging.DEBUG, stream=sys.__stderr__, format=fmt)
27
+ faulthandler.enable(file=sys.__stderr__)
28
+ if hasattr(signal, "SIGUSR1"): faulthandler.register(signal.SIGUSR1, file=sys.__stderr__)
29
+
30
+
31
+ def trace_msg(logger, prefix: str, msg: dict, *, enabled: bool = True):
32
+ "Log message flow at high level: msg_type, msg_id, subshell_id."
33
+ if not enabled: return
34
+ h = msg.get("header") or {}
35
+ logger.warning("%s type=%s id=%s subshell=%r", prefix, h.get("msg_type"), h.get("msg_id"), h.get("subshell_id"))
36
+
37
+
38
+ def murmur2_x86(data: str, seed: int) -> int:
39
+ "Return Murmur2 x86 hash of UTF-8 `data` with `seed`."
40
+ m = 0x5BD1E995
41
+ data_bytes = data.encode("utf-8")
42
+ length = len(data_bytes)
43
+ h = seed ^ length
44
+ rounded_end = length & 0xFFFFFFFC
45
+ for i in range(0, rounded_end, 4):
46
+ k = int.from_bytes(data_bytes[i : i + 4], "little")
47
+ k = (k * m) & 0xFFFFFFFF
48
+ k ^= k >> 24
49
+ k = (k * m) & 0xFFFFFFFF
50
+
51
+ h = (h * m) & 0xFFFFFFFF
52
+ h ^= k
53
+
54
+ val = length & 0x03
55
+ k = 0
56
+ if val >= 3: k = data_bytes[rounded_end + 2] << 16
57
+ if val >= 2: k |= data_bytes[rounded_end + 1] << 8
58
+ if val >= 1:
59
+ k |= data_bytes[rounded_end]
60
+ h ^= k
61
+ h = (h * m) & 0xFFFFFFFF
62
+
63
+ h ^= h >> 13
64
+ h = (h * m) & 0xFFFFFFFF
65
+ h ^= h >> 15
66
+ return h
67
+
68
+
69
+ DEBUG_HASH_SEED = 0xC70F6907
70
+
71
+
72
+ def debug_tmp_directory() -> str: return os.path.join(tempfile.gettempdir(), f"kernmini_{os.getpid()}")
73
+
74
+
75
+ def debug_cell_filename(code: str, ext: str = ".py") -> str:
76
+ "Compute debug cell filename (hash matches ipykernel's scheme, so frontends map breakpoints); respects KERNMINI_CELL_NAME."
77
+ cell_name = os.environ.get("KERNMINI_CELL_NAME")
78
+ if cell_name is None:
79
+ name = murmur2_x86(code, DEBUG_HASH_SEED)
80
+ cell_name = os.path.join(debug_tmp_directory(), f"{name}{ext}")
81
+ return cell_name