repld-tool 0.2.1__tar.gz → 0.2.2__tar.gz
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.
- {repld_tool-0.2.1 → repld_tool-0.2.2}/PKG-INFO +1 -1
- {repld_tool-0.2.1 → repld_tool-0.2.2}/pyproject.toml +1 -1
- {repld_tool-0.2.1 → repld_tool-0.2.2}/src/repld/__init__.py +3 -1
- {repld_tool-0.2.1 → repld_tool-0.2.2}/src/repld/help.py +11 -0
- {repld_tool-0.2.1 → repld_tool-0.2.2}/src/repld/kernel.py +43 -9
- {repld_tool-0.2.1 → repld_tool-0.2.2}/src/repld/runtime.py +11 -3
- {repld_tool-0.2.1 → repld_tool-0.2.2}/LICENSE +0 -0
- {repld_tool-0.2.1 → repld_tool-0.2.2}/README.md +0 -0
- {repld_tool-0.2.1 → repld_tool-0.2.2}/src/repld/.ruff_cache/.gitignore +0 -0
- {repld_tool-0.2.1 → repld_tool-0.2.2}/src/repld/.ruff_cache/0.15.22/14226964882126921391 +0 -0
- {repld_tool-0.2.1 → repld_tool-0.2.2}/src/repld/.ruff_cache/CACHEDIR.TAG +0 -0
- {repld_tool-0.2.1 → repld_tool-0.2.2}/src/repld/__main__.py +0 -0
- {repld_tool-0.2.1 → repld_tool-0.2.2}/src/repld/bg.py +0 -0
- {repld_tool-0.2.1 → repld_tool-0.2.2}/src/repld/bind.py +0 -0
- {repld_tool-0.2.1 → repld_tool-0.2.2}/src/repld/bridge.py +0 -0
- {repld_tool-0.2.1 → repld_tool-0.2.2}/src/repld/bridge_tools.py +0 -0
- {repld_tool-0.2.1 → repld_tool-0.2.2}/src/repld/browser/__init__.py +0 -0
- {repld_tool-0.2.1 → repld_tool-0.2.2}/src/repld/browser/browser.py +0 -0
- {repld_tool-0.2.1 → repld_tool-0.2.2}/src/repld/browser/capture.py +0 -0
- {repld_tool-0.2.1 → repld_tool-0.2.2}/src/repld/browser/cdp.py +0 -0
- {repld_tool-0.2.1 → repld_tool-0.2.2}/src/repld/browser/har.py +0 -0
- {repld_tool-0.2.1 → repld_tool-0.2.2}/src/repld/browser/observe.py +0 -0
- {repld_tool-0.2.1 → repld_tool-0.2.2}/src/repld/browser/pin.py +0 -0
- {repld_tool-0.2.1 → repld_tool-0.2.2}/src/repld/browser/png.py +0 -0
- {repld_tool-0.2.1 → repld_tool-0.2.2}/src/repld/browser/pool.py +0 -0
- {repld_tool-0.2.1 → repld_tool-0.2.2}/src/repld/browser/row.py +0 -0
- {repld_tool-0.2.1 → repld_tool-0.2.2}/src/repld/browser/selector.py +0 -0
- {repld_tool-0.2.1 → repld_tool-0.2.2}/src/repld/browser/session.py +0 -0
- {repld_tool-0.2.1 → repld_tool-0.2.2}/src/repld/browser/tab.py +0 -0
- {repld_tool-0.2.1 → repld_tool-0.2.2}/src/repld/browser/tab_query.py +0 -0
- {repld_tool-0.2.1 → repld_tool-0.2.2}/src/repld/browser/target.py +0 -0
- {repld_tool-0.2.1 → repld_tool-0.2.2}/src/repld/browser_dispatch.py +0 -0
- {repld_tool-0.2.1 → repld_tool-0.2.2}/src/repld/channel.py +0 -0
- {repld_tool-0.2.1 → repld_tool-0.2.2}/src/repld/cli.py +0 -0
- {repld_tool-0.2.1 → repld_tool-0.2.2}/src/repld/cli_args.py +0 -0
- {repld_tool-0.2.1 → repld_tool-0.2.2}/src/repld/core_schemas.py +0 -0
- {repld_tool-0.2.1 → repld_tool-0.2.2}/src/repld/dashboard.py +0 -0
- {repld_tool-0.2.1 → repld_tool-0.2.2}/src/repld/dashboard_cmd.py +0 -0
- {repld_tool-0.2.1 → repld_tool-0.2.2}/src/repld/dashboard_html.py +0 -0
- {repld_tool-0.2.1 → repld_tool-0.2.2}/src/repld/display.py +0 -0
- {repld_tool-0.2.1 → repld_tool-0.2.2}/src/repld/eventlog.py +0 -0
- {repld_tool-0.2.1 → repld_tool-0.2.2}/src/repld/events.py +0 -0
- {repld_tool-0.2.1 → repld_tool-0.2.2}/src/repld/exec_cmd.py +0 -0
- {repld_tool-0.2.1 → repld_tool-0.2.2}/src/repld/gate_cmd.py +0 -0
- {repld_tool-0.2.1 → repld_tool-0.2.2}/src/repld/gates.py +0 -0
- {repld_tool-0.2.1 → repld_tool-0.2.2}/src/repld/gist_api.py +0 -0
- {repld_tool-0.2.1 → repld_tool-0.2.2}/src/repld/gist_cmd.py +0 -0
- {repld_tool-0.2.1 → repld_tool-0.2.2}/src/repld/gist_deps.py +0 -0
- {repld_tool-0.2.1 → repld_tool-0.2.2}/src/repld/gist_links.py +0 -0
- {repld_tool-0.2.1 → repld_tool-0.2.2}/src/repld/gist_lint.py +0 -0
- {repld_tool-0.2.1 → repld_tool-0.2.2}/src/repld/gists.py +0 -0
- {repld_tool-0.2.1 → repld_tool-0.2.2}/src/repld/ipc.py +0 -0
- {repld_tool-0.2.1 → repld_tool-0.2.2}/src/repld/kernel_context.py +0 -0
- {repld_tool-0.2.1 → repld_tool-0.2.2}/src/repld/lifecycle_cmd.py +0 -0
- {repld_tool-0.2.1 → repld_tool-0.2.2}/src/repld/log_cmd.py +0 -0
- {repld_tool-0.2.1 → repld_tool-0.2.2}/src/repld/paths.py +0 -0
- {repld_tool-0.2.1 → repld_tool-0.2.2}/src/repld/protocol.py +0 -0
- {repld_tool-0.2.1 → repld_tool-0.2.2}/src/repld/relaunch.py +0 -0
- {repld_tool-0.2.1 → repld_tool-0.2.2}/src/repld/render.py +0 -0
- {repld_tool-0.2.1 → repld_tool-0.2.2}/src/repld/sessions.py +0 -0
- {repld_tool-0.2.1 → repld_tool-0.2.2}/src/repld/spawn.py +0 -0
- {repld_tool-0.2.1 → repld_tool-0.2.2}/src/repld/state.py +0 -0
- {repld_tool-0.2.1 → repld_tool-0.2.2}/src/repld/tasks.py +0 -0
|
@@ -11,7 +11,9 @@ if TYPE_CHECKING:
|
|
|
11
11
|
from typing import Any, Callable, Coroutine
|
|
12
12
|
|
|
13
13
|
def notify(content: Any, **meta: Any) -> None: ...
|
|
14
|
-
def defer(
|
|
14
|
+
def defer(
|
|
15
|
+
coro: Coroutine | Callable[[], Coroutine], label: str | None = None
|
|
16
|
+
) -> str: ...
|
|
15
17
|
def every(
|
|
16
18
|
seconds: float, *, label: str | None = None, delay: float = 0.0
|
|
17
19
|
) -> Callable: ...
|
|
@@ -1113,6 +1113,9 @@ no_display(value) → value
|
|
|
1113
1113
|
|
|
1114
1114
|
defer(coro, label=None) → task_id
|
|
1115
1115
|
Fire-and-forget. Channel push on done. Visible to get_task/cancel.
|
|
1116
|
+
coro can also be a zero-arg callable that builds the awaitable —
|
|
1117
|
+
defer(lambda: asyncio.gather(a(), b())) — so gather()/create_task()
|
|
1118
|
+
construct on the kernel's loop instead of a sync cell's own thread.
|
|
1116
1119
|
|
|
1117
1120
|
every(seconds, label=, delay=0)(fn) → fn periodic ticker; fn.cancel() stops
|
|
1118
1121
|
delay= defers the first tick (default: tick now)
|
|
@@ -1466,6 +1469,14 @@ For intentionally long work, use defer():
|
|
|
1466
1469
|
|
|
1467
1470
|
defer(download_all_invoices(), label="invoice sync")
|
|
1468
1471
|
|
|
1472
|
+
Fanning out with asyncio.gather()/create_task() inside defer() needs one
|
|
1473
|
+
more step: those reach for a running loop when *built*, not just when
|
|
1474
|
+
awaited, and a sync cell (no top-level await) runs in a worker thread with
|
|
1475
|
+
no loop of its own. Pass defer() a zero-arg callable instead of a
|
|
1476
|
+
pre-built awaitable, so the gather() call happens on the kernel's loop:
|
|
1477
|
+
|
|
1478
|
+
defer(lambda: asyncio.gather(one("a"), one("b"), return_exceptions=True))
|
|
1479
|
+
|
|
1469
1480
|
This returns the task_id immediately. The channel notification arrives
|
|
1470
1481
|
when the coroutine completes (or fails).
|
|
1471
1482
|
|
|
@@ -487,14 +487,29 @@ async def _run_cell(task_id: str, src: str, n: int, *, wait_ready: bool = True)
|
|
|
487
487
|
task["exception"] = type(exc).__name__
|
|
488
488
|
|
|
489
489
|
|
|
490
|
-
async def _run_deferred(task_id: str,
|
|
491
|
-
"""
|
|
492
|
-
|
|
493
|
-
|
|
490
|
+
async def _run_deferred(task_id: str, thunk) -> None:
|
|
491
|
+
"""Build and await a user-supplied awaitable within the task lifecycle.
|
|
492
|
+
|
|
493
|
+
`thunk` is always a zero-arg callable — defer() wraps a coroutine object
|
|
494
|
+
passed directly as ``lambda: coro``, so this only has one code path.
|
|
495
|
+
Calling it here, inside a coroutine already scheduled on the kernel's
|
|
496
|
+
loop (`asyncio.run_coroutine_threadsafe`), is what lets a factory like
|
|
497
|
+
``lambda: asyncio.gather(...)`` build its awaitable with a loop actually
|
|
498
|
+
running: `asyncio.gather()`/`create_task()`/`ensure_future()` all reach
|
|
499
|
+
for the *current thread's* loop at construction time, not just to await,
|
|
500
|
+
and a sync cell's body runs off-loop (`asyncio.to_thread`) to keep the
|
|
501
|
+
kernel responsive — so building one directly in the cell raises "no
|
|
502
|
+
current event loop" before defer() ever gets a look at it.
|
|
494
503
|
"""
|
|
495
504
|
async with _task_scope(task_id) as task:
|
|
496
505
|
try:
|
|
497
|
-
|
|
506
|
+
awaitable = thunk()
|
|
507
|
+
if not inspect.isawaitable(awaitable):
|
|
508
|
+
raise TypeError(
|
|
509
|
+
"defer() factory must return an awaitable, got "
|
|
510
|
+
f"{type(awaitable).__name__}"
|
|
511
|
+
)
|
|
512
|
+
await awaitable
|
|
498
513
|
except asyncio.CancelledError:
|
|
499
514
|
task["exception"] = "CancelledError"
|
|
500
515
|
except BaseException as exc:
|
|
@@ -508,13 +523,32 @@ def _make_defer(loop: asyncio.AbstractEventLoop):
|
|
|
508
523
|
def defer(coro, label: str | None = None) -> str:
|
|
509
524
|
"""Schedule a coroutine as a tracked task. Returns task_id immediately.
|
|
510
525
|
|
|
526
|
+
`coro` is normally a coroutine object (`defer(my_async_fn())`). It
|
|
527
|
+
may also be a zero-arg callable that *builds* one —
|
|
528
|
+
`defer(lambda: asyncio.gather(one(), two()))` — for constructs that
|
|
529
|
+
need a running loop to construct, not just to await. A sync cell's
|
|
530
|
+
body runs in a worker thread with no event loop set, so calling
|
|
531
|
+
`asyncio.gather(...)` there eagerly (as a `defer()` argument) fails
|
|
532
|
+
before `defer()` is even entered; wrapping it in a zero-arg callable
|
|
533
|
+
defers the call itself onto the kernel's loop, where it belongs.
|
|
534
|
+
|
|
511
535
|
The task is visible to get_task and cancel. On completion, a task_done
|
|
512
536
|
channel notification is pushed.
|
|
513
537
|
"""
|
|
514
|
-
if
|
|
538
|
+
if inspect.iscoroutine(coro):
|
|
539
|
+
thunk = lambda c=coro: c # noqa: E731
|
|
540
|
+
elif inspect.iscoroutinefunction(coro):
|
|
541
|
+
raise TypeError(
|
|
542
|
+
f"defer() expects a coroutine object, got the function "
|
|
543
|
+
f"{coro.__name__!r} itself. Call it as: defer({coro.__name__}())"
|
|
544
|
+
)
|
|
545
|
+
elif callable(coro):
|
|
546
|
+
thunk = coro
|
|
547
|
+
else:
|
|
515
548
|
raise TypeError(
|
|
516
|
-
|
|
517
|
-
"Call it as:
|
|
549
|
+
"defer() expects a coroutine object (or a zero-arg callable "
|
|
550
|
+
f"that builds one), got {type(coro).__name__}. Call it as: "
|
|
551
|
+
"defer(my_async_fn()) or defer(lambda: asyncio.gather(...))"
|
|
518
552
|
)
|
|
519
553
|
# Inherit the calling cell's originating session so a background task
|
|
520
554
|
# reports back to whoever asked for it. defer() from an @every body or
|
|
@@ -527,7 +561,7 @@ def _make_defer(loop: asyncio.AbstractEventLoop):
|
|
|
527
561
|
task["label"] = label
|
|
528
562
|
src_label = label or "..."
|
|
529
563
|
events.emit(CellStart(task_id, f"defer({src_label})", time.time()))
|
|
530
|
-
asyncio.run_coroutine_threadsafe(_run_deferred(task_id,
|
|
564
|
+
asyncio.run_coroutine_threadsafe(_run_deferred(task_id, thunk), loop)
|
|
531
565
|
return task_id
|
|
532
566
|
|
|
533
567
|
return defer
|
|
@@ -221,8 +221,16 @@ def _format_user_traceback(exc: BaseException) -> str:
|
|
|
221
221
|
)
|
|
222
222
|
elif "no current event loop" in msg or "no running event loop" in msg:
|
|
223
223
|
formatted += (
|
|
224
|
-
"\nHint: this cell ran in a background thread "
|
|
225
|
-
"
|
|
226
|
-
"
|
|
224
|
+
"\nHint: this cell ran in a background thread (no top-level "
|
|
225
|
+
"'await' in the cell), so asyncio.gather()/create_task()/"
|
|
226
|
+
"ensure_future() had no loop to attach to — they reach for "
|
|
227
|
+
"one at construction, not just to await it. Build it inside "
|
|
228
|
+
"an async def instead:\n"
|
|
229
|
+
" async def _run():\n"
|
|
230
|
+
" return await asyncio.gather(a(), b())\n"
|
|
231
|
+
" await _run()\n"
|
|
232
|
+
"or, if deferring it, pass defer() a zero-arg callable so it "
|
|
233
|
+
"builds on the kernel's loop rather than in this thread:\n"
|
|
234
|
+
" defer(lambda: asyncio.gather(a(), b()))\n"
|
|
227
235
|
)
|
|
228
236
|
return formatted
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|