Skip to content

Suspense

Primitives behind PythonNative's async rendering model: the Suspend signal, the CoroDriver that steps async def component bodies synchronously, cached async values (Resource / start_resource), and code splitting with lazy.

The user-facing pieces are the Suspense boundary component (documented with the other components) and the use_resource hook (documented with the other hooks); this page covers the underlying machinery.

Suspense primitives: suspension signals, resources, and lazy loading.

This module implements the machinery behind PythonNative's async rendering model:

  • async def components: a component body may be a coroutine. The reconciler drives it synchronously as far as possible (awaits on already-resolved futures complete inline); if it blocks on pending work, the render suspends and the nearest Suspense boundary shows its fallback until the coroutine finishes.
  • use_resource: a hook that starts an async fetch and caches it across renders. Reading an unresolved Resource suspends the render; awaiting it inside an async def component does the same thing without the boundary having to re-run anything by hand.
  • lazy: code-splitting for components. The loader runs once, and renders suspend until it resolves.

The core building block is CoroDriver, a miniature task that steps a coroutine synchronously until it either finishes or blocks on a pending :class:asyncio.Future. This is what lets an async def component whose awaits are all cached (resolved resources, completed tasks) render in a single synchronous pass with zero event-loop round trips, while genuinely pending awaits suspend cleanly.

Classes:

Name Description
Suspend

Signal that a render is blocked on pending async work.

CoroDriver

Drive a coroutine synchronously until it blocks on pending work.

Resource

A cached async value with Suspense integration.

Functions:

Name Description
start_resource

Start fetcher immediately and wrap it in a Resource.

lazy

Define a component that loads its implementation on first render.

Suspend

Suspend(waitable: Any, hook_state: Any = None, label: str = '')

Bases: BaseException

Signal that a render is blocked on pending async work.

Raised while a component renders, either by Resource.read or by an async def component body blocking on a pending await. The reconciler catches it: the nearest Suspense boundary shows its fallback (initial mounts), or the component keeps its previous content and re-renders when the work finishes (updates).

Derives from :class:BaseException so ErrorBoundary (which catches :class:Exception) never mistakes a suspension for a crash.

Attributes:

Name Type Description
waitable

The pending work; exposes done() and add_done_callback(cb).

hook_state

The suspended component's hook state, carried so a Suspense boundary can preserve it across retries (its cached resources survive, so the retry doesn't refetch).

key Optional[Tuple[int, Any]]

(component identity, element key) used to re-match hook_state on retry.

label

Component name for diagnostics.

CoroDriver

CoroDriver(coro: Any, context: Optional[Context] = None)

Drive a coroutine synchronously until it blocks on pending work.

A miniature :class:asyncio.Task: it steps the coroutine with send(), resolving awaits on already-done futures inline. When the coroutine blocks on a pending future, the driver parks and resumes from that future's done callback (on the framework loop's thread). Each step runs inside the :mod:contextvars context captured at creation, so hooks called after an await still see the owning component's hook state.

Attributes:

Name Type Description
done

Whether the coroutine finished (returned, raised, or was cancelled).

Methods:

Name Description
cancelled

Whether the driver was cancelled before finishing normally.

result

Return the coroutine's return value (raises if not done or failed).

exception

Return the coroutine's exception, or None.

add_done_callback

Invoke callback(self) when the coroutine finishes.

start

Run the coroutine as far as it can go without blocking.

cancel

Throw :class:asyncio.CancelledError into the coroutine.

cancelled

cancelled() -> bool

Whether the driver was cancelled before finishing normally.

result

result() -> Any

Return the coroutine's return value (raises if not done or failed).

exception

exception() -> Optional[BaseException]

Return the coroutine's exception, or None.

add_done_callback

add_done_callback(callback: Callable[['CoroDriver'], None]) -> None

Invoke callback(self) when the coroutine finishes.

Fires immediately when already done. Callbacks run on whatever thread finished the coroutine (the framework loop's thread for suspended coroutines).

start

start() -> None

Run the coroutine as far as it can go without blocking.

cancel

cancel() -> bool

Throw :class:asyncio.CancelledError into the coroutine.

Returns:

Type Description
bool

False when the coroutine had already finished,

bool

True otherwise.

Resource

Resource(driver: CoroDriver)

Bases: Generic[T]

A cached async value with Suspense integration.

Returned by use_resource. A resource starts fetching as soon as the hook runs and remembers its result across renders (until its dependencies change), so re-renders never refetch.

Two ways to consume it:

  • resource.read() in a regular component: returns the value when ready, re-raises the fetcher's error if it failed, and suspends the render while pending.
  • await resource in an async def component: same semantics, expressed as a plain await.

Methods:

Name Description
read

Return the fetched value, or suspend the render while pending.

cancel

Cancel the in-flight fetch (no-op when already done).

Attributes:

Name Type Description
ready bool

Whether the fetch has finished (successfully or not).

ready property

ready: bool

Whether the fetch has finished (successfully or not).

read

read() -> T

Return the fetched value, or suspend the render while pending.

Raises:

Type Description
Suspend

While the fetch is still in flight (caught by the reconciler, never by user code).

BaseException

Whatever the fetcher raised, re-raised so an enclosing ErrorBoundary can catch it.

cancel

cancel() -> None

Cancel the in-flight fetch (no-op when already done).

start_resource

start_resource(fetcher: Callable[[], Any]) -> Resource[Any]

Start fetcher immediately and wrap it in a Resource.

The fetcher may be an async def (typical) or a plain function; synchronous results resolve the resource immediately, so reading it never suspends.

This is the non-hook constructor used for module-level resources (preloading data before a screen mounts) and by lazy. Inside components, prefer use_resource, which caches per component instance and re-fetches when dependencies change.

lazy

lazy(loader: Callable[[], Any]) -> Callable[..., Any]

Define a component that loads its implementation on first render.

loader runs once, the first time the returned component renders; until it resolves, renders suspend (so wrap usages in a Suspense boundary to show a loading state). The loaded value must be a component (a @component function or any element factory).

Parameters:

Name Type Description Default
loader Callable[[], Any]

A zero-arg callable returning the component, or an async def resolving to it. Synchronous loaders (a deferred import) resolve immediately and never suspend.

required

Returns:

Type Description
Callable[..., Any]

A component. Props (and children) pass through to the loaded

Callable[..., Any]

component unchanged.

Example
import pythonnative as pn

Chart = pn.lazy(lambda: __import__("app.chart", fromlist=["Chart"]).Chart)

@pn.component
def Dashboard():
    return pn.Suspense(
        Chart(points=[1, 2, 3]),
        fallback=pn.ActivityIndicator(),
    )

Next steps

  • Walk through async components, Suspense, and use_resource end-to-end: Async + data guide.
  • See how suspension threads through the render pass: Architecture.