live_print_scope
live_print_scope stores per-scope metadata in a contextvars ContextVar. print_to_live and log_to_live read the active scope: they prepend prefix to each line and skip output when suppress is true.
Use it when parallel work (for example tfdo multi-directory orchestration) needs tagged scroll lines without editing every print_to_live call site. Pair it with run_pool: submit copies the submitter's context into the worker thread. With run_and_wait, message_callbacks see the scope active when the run was created.
Read the active scope
from ask_shell.console import get_live_print_context, live_print_scope
with live_print_scope(prefix="[prod] "):
ctx = get_live_print_context()
print(repr(ctx.prefix if ctx else ""))
#> '[prod] '
Suppress live output
from ask_shell.console import get_live_print_context, live_print_scope
with live_print_scope(suppress=True):
ctx = get_live_print_context()
print(ctx.suppress if ctx else False)
#> True
Nested scopes restore the outer context when the inner block exits.
from ask_shell.console import get_live_print_context, live_print_scope
with live_print_scope(prefix="outer "):
outer = get_live_print_context()
with live_print_scope(prefix="inner "):
inner = get_live_print_context()
after = get_live_print_context()
print(f"{outer.prefix}|{inner.prefix}|{after.prefix}")
#> outer |inner |outer
Context at run_pool submit time
Set live_print_scope in the thread that calls submit. Each worker sees the scope that was active for its submit call.
from threading import Barrier
from ask_shell.console import get_live_print_context, live_print_scope
from ask_shell.shell import run_pool
results: list[str] = []
barrier = Barrier(2)
def worker() -> None:
barrier.wait()
ctx = get_live_print_context()
results.append(ctx.prefix if ctx else "")
with run_pool("demo", total=2, pool_thread_count=2, max_concurrent_submits=2) as pool:
with live_print_scope(prefix="a"):
f_a = pool.submit(worker)
with live_print_scope(prefix="b"):
f_b = pool.submit(worker)
f_a.result()
f_b.result()
print(sorted(results))
#> ['a', 'b']
Context with run_and_wait and message_callbacks
ShellRun snapshots the active context when run / run_and_wait creates it. The queue consumer runs message_callbacks inside that snapshot, so handlers see the same live_print_scope as the caller even though dispatch runs on a pool thread.
from ask_shell._internal._run import run_and_wait
from ask_shell._internal.events import ShellRunStdOutput
from ask_shell.console import get_live_print_context, live_print_scope
seen: list[str | None] = []
def on_stdout(message):
match message:
case ShellRunStdOutput(is_stdout=True):
ctx = get_live_print_context()
seen.append(ctx.prefix if ctx else None)
return False
with live_print_scope(prefix="[dir] "):
run_and_wait("echo ok", message_callbacks=[on_stdout])
print(seen)
#> ['[dir] ']
The snapshot is fixed for the life of that ShellRun; changing live_print_scope after run_and_wait returns does not affect callbacks for an in-flight run. Retries reuse the same snapshot.