pdit 0.6.0__py3-none-any.whl → 0.7.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.
- pdit/_static/assets/{index-DbESVfT-.js → index-WLaVrqLo.js} +28 -28
- pdit/_static/index.html +1 -1
- pdit/exporter.py +39 -4
- pdit/ipython_executor.py +28 -7
- pdit/server.py +21 -2
- {pdit-0.6.0.dist-info → pdit-0.7.0.dist-info}/METADATA +1 -1
- pdit-0.7.0.dist-info/RECORD +17 -0
- pdit-0.6.0.dist-info/RECORD +0 -17
- {pdit-0.6.0.dist-info → pdit-0.7.0.dist-info}/WHEEL +0 -0
- {pdit-0.6.0.dist-info → pdit-0.7.0.dist-info}/entry_points.txt +0 -0
- {pdit-0.6.0.dist-info → pdit-0.7.0.dist-info}/licenses/LICENSE +0 -0
- {pdit-0.6.0.dist-info → pdit-0.7.0.dist-info}/top_level.txt +0 -0
pdit/_static/index.html
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
|
|
6
6
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
7
7
|
<title>pdit</title>
|
|
8
|
-
<script type="module" crossorigin src="/assets/index-
|
|
8
|
+
<script type="module" crossorigin src="/assets/index-WLaVrqLo.js"></script>
|
|
9
9
|
<link rel="stylesheet" crossorigin href="/assets/index-rS96z8hq.css">
|
|
10
10
|
</head>
|
|
11
11
|
<body>
|
pdit/exporter.py
CHANGED
|
@@ -1,13 +1,15 @@
|
|
|
1
1
|
"""Export functionality for pdit scripts."""
|
|
2
2
|
|
|
3
|
+
import asyncio
|
|
3
4
|
import json
|
|
4
5
|
from pathlib import Path
|
|
5
|
-
from
|
|
6
|
+
from threading import Thread
|
|
7
|
+
from typing import Any, Callable, Coroutine
|
|
6
8
|
|
|
7
9
|
from .ipython_executor import IPythonExecutor
|
|
8
10
|
|
|
9
11
|
|
|
10
|
-
def
|
|
12
|
+
async def _execute_script_async(script_content: str, script_name: str) -> list[dict[str, Any]]:
|
|
11
13
|
"""Execute a script and return expressions in frontend format.
|
|
12
14
|
|
|
13
15
|
Args:
|
|
@@ -22,7 +24,7 @@ def execute_script(script_content: str, script_name: str) -> list[dict[str, Any]
|
|
|
22
24
|
expression_id = 0
|
|
23
25
|
|
|
24
26
|
try:
|
|
25
|
-
for event in executor.execute_script(script_content, script_name=script_name):
|
|
27
|
+
async for event in executor.execute_script(script_content, script_name=script_name):
|
|
26
28
|
# Skip the expressions list event
|
|
27
29
|
if event.get("type") == "expressions":
|
|
28
30
|
continue
|
|
@@ -40,11 +42,44 @@ def execute_script(script_content: str, script_name: str) -> list[dict[str, Any]
|
|
|
40
42
|
})
|
|
41
43
|
expression_id += 1
|
|
42
44
|
finally:
|
|
43
|
-
executor.shutdown()
|
|
45
|
+
await executor.shutdown()
|
|
44
46
|
|
|
45
47
|
return expressions
|
|
46
48
|
|
|
47
49
|
|
|
50
|
+
def _run_async(
|
|
51
|
+
async_fn: Callable[..., Coroutine[Any, Any, Any]],
|
|
52
|
+
*args: Any,
|
|
53
|
+
**kwargs: Any,
|
|
54
|
+
) -> Any:
|
|
55
|
+
try:
|
|
56
|
+
asyncio.get_running_loop()
|
|
57
|
+
except RuntimeError:
|
|
58
|
+
return asyncio.run(async_fn(*args, **kwargs))
|
|
59
|
+
|
|
60
|
+
result: dict[str, Any] = {}
|
|
61
|
+
error: dict[str, BaseException] = {}
|
|
62
|
+
|
|
63
|
+
def runner() -> None:
|
|
64
|
+
try:
|
|
65
|
+
result["value"] = asyncio.run(async_fn(*args, **kwargs))
|
|
66
|
+
except BaseException as exc:
|
|
67
|
+
error["value"] = exc
|
|
68
|
+
|
|
69
|
+
thread = Thread(target=runner, daemon=True)
|
|
70
|
+
thread.start()
|
|
71
|
+
thread.join()
|
|
72
|
+
|
|
73
|
+
if "value" in error:
|
|
74
|
+
raise error["value"]
|
|
75
|
+
return result["value"]
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def execute_script(script_content: str, script_name: str) -> list[dict[str, Any]]:
|
|
79
|
+
"""Execute a script and return expressions in frontend format."""
|
|
80
|
+
return _run_async(_execute_script_async, script_content, script_name)
|
|
81
|
+
|
|
82
|
+
|
|
48
83
|
def generate_html(script_content: str, expressions: list[dict[str, Any]]) -> str:
|
|
49
84
|
"""Generate self-contained HTML from script and execution results.
|
|
50
85
|
|
pdit/ipython_executor.py
CHANGED
|
@@ -12,7 +12,7 @@ import json
|
|
|
12
12
|
import logging
|
|
13
13
|
import re
|
|
14
14
|
import traceback
|
|
15
|
-
from typing import Any, AsyncGenerator, Optional
|
|
15
|
+
from typing import Any, AsyncGenerator, Awaitable, Callable, Optional
|
|
16
16
|
|
|
17
17
|
from jupyter_client import AsyncKernelManager
|
|
18
18
|
|
|
@@ -234,8 +234,17 @@ del _register_pdit_runtime_hooks
|
|
|
234
234
|
|
|
235
235
|
return output
|
|
236
236
|
|
|
237
|
-
async def _execute_code(
|
|
238
|
-
|
|
237
|
+
async def _execute_code(
|
|
238
|
+
self,
|
|
239
|
+
code: str,
|
|
240
|
+
on_stream: Callable[[list[dict]], Awaitable[None]] | None = None,
|
|
241
|
+
) -> list[dict]:
|
|
242
|
+
"""Execute code in kernel and collect output.
|
|
243
|
+
|
|
244
|
+
Args:
|
|
245
|
+
code: Python source to execute in the kernel.
|
|
246
|
+
on_stream: Optional callback invoked when stdout/stderr updates arrive.
|
|
247
|
+
"""
|
|
239
248
|
if self.kc is None:
|
|
240
249
|
return [{"type": "error", "content": "Kernel not started"}]
|
|
241
250
|
|
|
@@ -265,6 +274,8 @@ del _register_pdit_runtime_hooks
|
|
|
265
274
|
output[-1] = {"type": stream_name, "content": output[-1]["content"] + text}
|
|
266
275
|
else:
|
|
267
276
|
output.append({"type": stream_name, "content": text})
|
|
277
|
+
if on_stream:
|
|
278
|
+
await on_stream(output)
|
|
268
279
|
elif msg_type == 'execute_result':
|
|
269
280
|
# Expression result
|
|
270
281
|
data = content['data']
|
|
@@ -291,7 +302,8 @@ del _register_pdit_runtime_hooks
|
|
|
291
302
|
self,
|
|
292
303
|
script: str,
|
|
293
304
|
line_range: tuple[int, int] | None = None,
|
|
294
|
-
script_name: str | None = None
|
|
305
|
+
script_name: str | None = None,
|
|
306
|
+
on_stream: Callable[[int, int, list[dict]], Awaitable[None]] | None = None,
|
|
295
307
|
) -> AsyncGenerator[dict, None]:
|
|
296
308
|
"""Execute Python script, yielding event dicts as each statement completes.
|
|
297
309
|
|
|
@@ -299,6 +311,7 @@ del _register_pdit_runtime_hooks
|
|
|
299
311
|
First: {"type": "expressions", "expressions": [{"lineStart": N, "lineEnd": N}, ...]}
|
|
300
312
|
Then for each statement: {"lineStart": N, "lineEnd": N, "output": [...], "isInvisible": bool}
|
|
301
313
|
On error during execution, the final result dict will have output with type="error"
|
|
314
|
+
If on_stream is provided, it is invoked on stdout/stderr updates with the current output list.
|
|
302
315
|
"""
|
|
303
316
|
# Wait for kernel to be ready
|
|
304
317
|
await self.wait_ready()
|
|
@@ -344,19 +357,27 @@ del _register_pdit_runtime_hooks
|
|
|
344
357
|
|
|
345
358
|
# Execute each statement
|
|
346
359
|
for stmt in statements:
|
|
360
|
+
stream_cb = None
|
|
361
|
+
if on_stream is not None:
|
|
362
|
+
line_start = stmt["lineStart"]
|
|
363
|
+
line_end = stmt["lineEnd"]
|
|
364
|
+
|
|
365
|
+
async def stream_cb(updated_output: list[dict]) -> None:
|
|
366
|
+
await on_stream(line_start, line_end, updated_output)
|
|
367
|
+
|
|
347
368
|
if stmt["isMarkdownCell"]:
|
|
348
369
|
# For markdown cells, just return the string content
|
|
349
370
|
try:
|
|
350
371
|
value = ast.literal_eval(stmt["source"])
|
|
351
372
|
output = [{"type": "text/markdown", "content": str(value).strip()}]
|
|
352
373
|
except (ValueError, SyntaxError):
|
|
353
|
-
output = await self._execute_code(stmt["source"])
|
|
374
|
+
output = await self._execute_code(stmt["source"], on_stream=stream_cb)
|
|
354
375
|
elif stmt["isFStringMarkdown"]:
|
|
355
376
|
# Wrap f-string in Markdown() so it returns text/markdown directly
|
|
356
377
|
wrapper_code = f"__import__('IPython').display.Markdown({stmt['source']})"
|
|
357
|
-
output = await self._execute_code(wrapper_code)
|
|
378
|
+
output = await self._execute_code(wrapper_code, on_stream=stream_cb)
|
|
358
379
|
else:
|
|
359
|
-
output = await self._execute_code(stmt["source"])
|
|
380
|
+
output = await self._execute_code(stmt["source"], on_stream=stream_cb)
|
|
360
381
|
|
|
361
382
|
yield {
|
|
362
383
|
"lineStart": stmt["lineStart"],
|
pdit/server.py
CHANGED
|
@@ -174,7 +174,7 @@ async def websocket_session(websocket: WebSocket, sessionId: str, token: Optiona
|
|
|
174
174
|
|
|
175
175
|
Message Protocol (Server -> Client):
|
|
176
176
|
File events: {"type": "initial/fileChanged/fileDeleted", "path": "...", "content": "...", "timestamp": N}
|
|
177
|
-
Execution: {"type": "expressions/result/cancelled/complete/busy", ...}
|
|
177
|
+
Execution: {"type": "expressions/result/stream/cancelled/complete/busy", ...}
|
|
178
178
|
Errors: {"type": "error", "message": "..."}
|
|
179
179
|
"""
|
|
180
180
|
# Validate token if configured
|
|
@@ -290,7 +290,26 @@ async def _handle_ws_execute(websocket: WebSocket, session: Session, data: dict)
|
|
|
290
290
|
expressions: list[dict] = []
|
|
291
291
|
executed_count = 0
|
|
292
292
|
|
|
293
|
-
async
|
|
293
|
+
async def _send_stream_update(line_start: int, line_end: int, output: list[dict]) -> None:
|
|
294
|
+
if shutdown_event.is_set():
|
|
295
|
+
return
|
|
296
|
+
try:
|
|
297
|
+
await websocket.send_json({
|
|
298
|
+
"type": "stream",
|
|
299
|
+
"lineStart": line_start,
|
|
300
|
+
"lineEnd": line_end,
|
|
301
|
+
"output": output,
|
|
302
|
+
})
|
|
303
|
+
except Exception:
|
|
304
|
+
# Connection may have closed; ignore streaming failures
|
|
305
|
+
pass
|
|
306
|
+
|
|
307
|
+
async for event in session.executor.execute_script(
|
|
308
|
+
script,
|
|
309
|
+
line_range,
|
|
310
|
+
script_name,
|
|
311
|
+
on_stream=_send_stream_update,
|
|
312
|
+
):
|
|
294
313
|
# Add type field to result messages (executor yields without type)
|
|
295
314
|
if "output" in event and "type" not in event:
|
|
296
315
|
event = {"type": "result", **event}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
pdit/__init__.py,sha256=QpiMnN_x5G3QaeWMXQlCRUtCo35IEYZLPj-cz87WG_g,185
|
|
2
|
+
pdit/_demo.py,sha256=SMOGoiHBdMDq8pXRVK-uuTRFCFbRd47diUyRR8Y3wZY,2026
|
|
3
|
+
pdit/cli.py,sha256=3YkWz2Sg3-OszRssWkXbPEu3dDQAXMS8Z0c07bZ7gx8,9634
|
|
4
|
+
pdit/exporter.py,sha256=xDFkAzigh7dDDCSqPP6nI4WKlZ_xI1XIKC2QkcdpRVg,3792
|
|
5
|
+
pdit/file_watcher.py,sha256=1WlqKWjLCdgMZLdN836MEeWTisGCtZmvgRdOFN-jns8,5200
|
|
6
|
+
pdit/ipython_executor.py,sha256=KMBFg4Xl5M2iiV8kkpSFJUURv9XjKYl5Nf1y5HzivxQ,17244
|
|
7
|
+
pdit/server.py,sha256=V5uFMqPle7JGeMHWRjniiN0m1z7rK-pOBZ1tiPNPU5E,14566
|
|
8
|
+
pdit/_static/export.html,sha256=ITKX3MKoWQYoR8EqkMTfsEYjpNWfhVVi-Mdmw_jpxoY,332339
|
|
9
|
+
pdit/_static/index.html,sha256=iDl5_lp8ailmLfeKWcGvEDY_wI6_hS5uMnDGRGEu1-o,450
|
|
10
|
+
pdit/_static/assets/index-WLaVrqLo.js,sha256=6g0aNVXaBOwAISafLYvgP-0bpEVL-YuQs026ebsFE4w,815277
|
|
11
|
+
pdit/_static/assets/index-rS96z8hq.css,sha256=0EMq2r0to1VxGznyp18tWCd97_sSZhGVC3YF7QdBXEU,22855
|
|
12
|
+
pdit-0.7.0.dist-info/licenses/LICENSE,sha256=Y9tvBURRu-1aHUHY-tnbsDpPBTRYKWpIInVmaoOUkME,1071
|
|
13
|
+
pdit-0.7.0.dist-info/METADATA,sha256=3tr06V3Xyy1c9elzPK1x0ULeowwrUrhJChqef0lsST0,1859
|
|
14
|
+
pdit-0.7.0.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
|
|
15
|
+
pdit-0.7.0.dist-info/entry_points.txt,sha256=n9I4wGD7M0NQrPWJcTWOsJAzeS6bQJbTnUP8C3Sl-iY,39
|
|
16
|
+
pdit-0.7.0.dist-info/top_level.txt,sha256=wk6vel1ecJS4EZZ3U6Xue9OwDq-Tw8Pbvq_TRQz9eIQ,5
|
|
17
|
+
pdit-0.7.0.dist-info/RECORD,,
|
pdit-0.6.0.dist-info/RECORD
DELETED
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
pdit/__init__.py,sha256=QpiMnN_x5G3QaeWMXQlCRUtCo35IEYZLPj-cz87WG_g,185
|
|
2
|
-
pdit/_demo.py,sha256=SMOGoiHBdMDq8pXRVK-uuTRFCFbRd47diUyRR8Y3wZY,2026
|
|
3
|
-
pdit/cli.py,sha256=3YkWz2Sg3-OszRssWkXbPEu3dDQAXMS8Z0c07bZ7gx8,9634
|
|
4
|
-
pdit/exporter.py,sha256=i2emLqRQNPW92vFBTamLZyOhUTDU45_0QnOWG7PcIu8,2811
|
|
5
|
-
pdit/file_watcher.py,sha256=1WlqKWjLCdgMZLdN836MEeWTisGCtZmvgRdOFN-jns8,5200
|
|
6
|
-
pdit/ipython_executor.py,sha256=5YEBpJL7IjLfNve8hXa-2_cYawfP6ro5myNAx0n97ug,16325
|
|
7
|
-
pdit/server.py,sha256=URabdsLxiLUipkl8PTc5jgDjMlQV0v8QJTMnNNy0XKs,13941
|
|
8
|
-
pdit/_static/export.html,sha256=ITKX3MKoWQYoR8EqkMTfsEYjpNWfhVVi-Mdmw_jpxoY,332339
|
|
9
|
-
pdit/_static/index.html,sha256=wjUe3SHOs1g4hvQHUiKcwoKSkHyYs2GDdk3OqtdDMSA,450
|
|
10
|
-
pdit/_static/assets/index-DbESVfT-.js,sha256=vOC5-SuA-WA-ZxZ1TkbCtOwi0sfSGNu0c1fEF1-382c,814902
|
|
11
|
-
pdit/_static/assets/index-rS96z8hq.css,sha256=0EMq2r0to1VxGznyp18tWCd97_sSZhGVC3YF7QdBXEU,22855
|
|
12
|
-
pdit-0.6.0.dist-info/licenses/LICENSE,sha256=Y9tvBURRu-1aHUHY-tnbsDpPBTRYKWpIInVmaoOUkME,1071
|
|
13
|
-
pdit-0.6.0.dist-info/METADATA,sha256=gqrL2j5vzJcOcpw3R3EKxt_hH1jHTc2ewKBWhK4KcBY,1859
|
|
14
|
-
pdit-0.6.0.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
|
|
15
|
-
pdit-0.6.0.dist-info/entry_points.txt,sha256=n9I4wGD7M0NQrPWJcTWOsJAzeS6bQJbTnUP8C3Sl-iY,39
|
|
16
|
-
pdit-0.6.0.dist-info/top_level.txt,sha256=wk6vel1ecJS4EZZ3U6Xue9OwDq-Tw8Pbvq_TRQz9eIQ,5
|
|
17
|
-
pdit-0.6.0.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|