Skip to content

Bridge

The Python half of the native bridge: JSON codec, per-platform transports, protocol handshake, main-queue posting, and the single callback native uses to reach Python. App code never calls this module directly; the BridgeBackend, BridgeModule, and NativeScreenHost do.

The native bridge: one channel between Python and Swift / Kotlin.

Everything that crosses into native code goes through a Transport (apply a transaction, run a command, drive an animate request, or call a native module), and everything native sends back arrives at native_callback. The protocol is documented in docs/concepts/bridge.md.

Under pn preview the native side is a browser page, reached through WebTransport; the preview installs it with set_transport before any screen mounts. In headless tests there is no transport at all: native modules fall back to their Python implementations, and tests that want to exercise the bridge itself install a FakeTransport.

Modules:

Name Description
android

Android transport: the com.pythonnative.runtime.PNBridge class via Chaquopy.

codec

JSON encoding for the native bridge.

commits

Versioned commit validation and surface ownership.

fake

An in-process stand-in for the native side of the bridge.

ios

iOS transport: C-ABI calls into PythonNativeKit through ctypes.

list_store

Revisioned list patch semantics shared by validation and headless renderers.

web

WebSocket transport for the browser renderer.

Classes:

Name Description
Transport

The Python -> native half of the bridge.

Functions:

Name Description
get_transport

Return the active transport, creating the platform one on first use.

has_transport

Whether a transport exists or can be created without raising.

set_transport

Install a transport explicitly (tests) or reset with None.

handshake

Verify the native library speaks our protocol version.

native_callback

Single entry point for every native -> Python message.

Attributes:

Name Type Description
PROTOCOL_VERSION

The bridge protocol every runtime speaks; generated native contracts carry it.

PROTOCOL_VERSION module-attribute

PROTOCOL_VERSION = 6

The bridge protocol every runtime speaks; generated native contracts carry it.

Transport

Bases: Protocol

The Python -> native half of the bridge.

Native and browser transports run Yoga beside their widgets. Only the headless FakeTransport lays out in Python, so it alone also answers measure and accepts frame (f) operations.

Methods:

Name Description
protocol_version

Return the protocol version compiled into the native library.

apply

Apply one serialized transaction (a JSON array of ops).

command

Run an imperative command on one view; returns its JSON result or None.

animate

Handle an animation request (set / start / cancel) for one view.

call

Call a native module method with a {"call_id", "args"} envelope.

set_callback

Install callback as the native -> Python entry point.

protocol_version

protocol_version() -> int

Return the protocol version compiled into the native library.

apply

apply(transaction_json: str) -> str

Apply one serialized transaction (a JSON array of ops).

command

command(
    tag: int, name: str, args_json: str
) -> Optional[str]

Run an imperative command on one view; returns its JSON result or None.

animate

animate(tag: int, request_json: str) -> Optional[str]

Handle an animation request (set / start / cancel) for one view.

call

call(
    module: str, method: str, args_json: str
) -> Optional[str]

Call a native module method with a {"call_id", "args"} envelope.

set_callback

set_callback(
    callback: Callable[[str, int, str, str], None],
) -> None

Install callback as the native -> Python entry point.

get_transport

get_transport() -> Transport

Return the active transport, creating the platform one on first use.

Raises:

Type Description
RuntimeError

Off-device, where no native runtime exists.

has_transport

has_transport() -> bool

Whether a transport exists or can be created without raising.

set_transport

set_transport(transport: Optional[Transport]) -> None

Install a transport explicitly (tests) or reset with None.

handshake

handshake() -> int

Verify the native library speaks our protocol version.

Called by the native templates right after Python starts. Returns the negotiated version.

Raises:

Type Description
RuntimeError

On a version mismatch, with a hint to rebuild.

native_callback

native_callback(
    kind: str, tag: int, name: str, payload: str
) -> None

Single entry point for every native -> Python message.

Native delivers every message asynchronously, from its events thread, and never waits for a result. This function never raises: failures are reported through diagnostics so nothing propagates into UIKit or the Android looper.

Parameters:

Name Type Description Default
kind str

"event", "module", "host", "animation", or "layout".

required
tag int

View tag (events), screen id (host), otherwise 0.

required
name str

Event name, module name, or host event.

required
payload str

JSON text whose shape depends on kind.

required

Codec

JSON encoding for the native bridge.

The bridge speaks JSON in both directions (see docs/concepts/bridge.md). Prop values coming out of the reconciler are almost, but not quite, JSON: they may contain frozenset event-name sets, tuples, floats that are infinite, and Python callables that native must never see. to_jsonable normalizes a value into something json.dumps accepts. Callbacks and declared Python-only fields stay in the backend's sidecar; unsupported native values raise an error.

Functions:

Name Description
to_jsonable

Return a JSON-serializable copy of value.

split_props

Split props into (wire_props, python_props).

build_transaction

Build the wire operations for a mutation batch, JSON-ready but not yet serialized.

encode_transaction

Encode a mutation batch as the bridge's transaction JSON.

dumps

Compact json.dumps used for every bridge payload.

loads

Parse a bridge payload (str or UTF-8 bytes); empty / None input yields None.

Attributes:

Name Type Description
INF

Wire spelling of math.inf (JSON has no infinity literal).

NEG_INF

Wire spelling of -math.inf.

INF module-attribute

INF = 'inf'

Wire spelling of math.inf (JSON has no infinity literal).

NEG_INF module-attribute

NEG_INF = '-inf'

Wire spelling of -math.inf.

to_jsonable

to_jsonable(value: Any) -> Any

Return a JSON-serializable copy of value.

  • set / frozenset / tuple become lists (sets are sorted when their members are strings so the output is deterministic).
  • Infinite floats become the strings "inf" / "-inf"; NaN becomes None (native clamps missing geometry to zero).
  • Dataclass-like objects with to_json() are converted through it.

Raises:

Type Description
TypeError

When value (or something nested in it) has no JSON representation.

split_props

split_props(
    props: Dict[str, Any],
    python_only: frozenset[str] = frozenset(),
) -> Tuple[Dict[str, Any], Dict[str, Any]]

Split props into (wire_props, python_props).

wire_props is JSON-ready; python_props holds callbacks and fields explicitly declared Python-only. Other unsupported values fail encoding. The backend keeps Python props in a per-tag sidecar. (The reconciler has already published Animated.event bindings under _pn_animated_events before props reach the codec.)

build_transaction

build_transaction(
    ops: Sequence[Mutation],
    types: Mapping[int, str] | None = None,
) -> Tuple[List[Any], List[Tuple[int, Dict[str, Any]]]]

Build the wire operations for a mutation batch, JSON-ready but not yet serialized.

Parameters:

Name Type Description Default
ops Sequence[Mutation]

Ordered mutations from the reconciler.

required
types Mapping[int, str] | None

Native component names for tags, used to identify Python-only props.

None

Returns:

Type Description
List[Any]

(wire_ops, python_props) where wire_ops is the list the

List[Tuple[int, Dict[str, Any]]]

transaction envelope carries under "ops" (every value already

Tuple[List[Any], List[Tuple[int, Dict[str, Any]]]]

normalized by to_jsonable)

Tuple[List[Any], List[Tuple[int, Dict[str, Any]]]]

and python_props lists (tag, props) pairs for values that

Tuple[List[Any], List[Tuple[int, Dict[str, Any]]]]

stayed Python-side (for c and u ops). The backend removes

Tuple[List[Any], List[Tuple[int, Dict[str, Any]]]]

sidecar entries using UNSET values in the original update

Tuple[List[Any], List[Tuple[int, Dict[str, Any]]]]

operations.

encode_transaction

encode_transaction(
    ops: Sequence[Mutation],
    types: Mapping[int, str] | None = None,
) -> Tuple[str, List[Tuple[int, Dict[str, Any]]]]

Encode a mutation batch as the bridge's transaction JSON.

A convenience over build_transaction for callers that want the serialized op list alone (tests and tooling); the backend serializes the whole envelope itself.

Returns:

Type Description
Tuple[str, List[Tuple[int, Dict[str, Any]]]]

(json_text, python_props); see build_transaction.

dumps

dumps(value: Any) -> str

Compact json.dumps used for every bridge payload.

loads

loads(text: Union[str, bytes, bytearray, None]) -> Any

Parse a bridge payload (str or UTF-8 bytes); empty / None input yields None.

Fake transport (tests)

An in-process stand-in for the native side of the bridge.

FakeTransport decodes the same JSON the Swift and Kotlin runtimes receive and keeps a tiny view tree so tests can assert on what would have reached native: created types and props, insert order, frames, commands, animation requests, and module calls. It also lets tests play the native side, firing events and module results back through native_callback.

Classes:

Name Description
FakeNativeView

One decoded native view: type, merged props, children, and frame.

FakeTransport

Decode bridge traffic into inspectable Python structures.

ModuleHandler module-attribute

ModuleHandler = Callable[[str, Dict[str, Any]], Any]

handler(method, args) -> value for a fake native module.

FakeNativeView

FakeNativeView(
    tag: int, type_name: str, props: Dict[str, Any]
)

One decoded native view: type, merged props, children, and frame.

FakeTransport

FakeTransport(
    *,
    version: int = PROTOCOL_VERSION,
    measure: Optional[Dict[str, Tuple[float, float]]] = None
)

Decode bridge traffic into inspectable Python structures.

Attributes:

Name Type Description
views Dict[int, FakeNativeView]

Live views by tag.

transactions List[List[Any]]

Every decoded transaction (list of op lists).

commands List[Tuple[int, str, Dict[str, Any]]]

(tag, name, args) tuples in call order.

animations List[Tuple[int, Dict[str, Any]]]

(tag, request) tuples in call order.

calls List[Tuple[str, str, Dict[str, Any]]]

(module, method, args) tuples in call order.

Methods:

Name Description
protocol_version

Return the protocol version compiled into the native library.

set_callback

Install callback as the native -> Python entry point.

apply

Validate a full revision before applying any native mutations.

measure

Return the intrinsic (width, height) of the view tag under the constraints.

command

Run an imperative command on one view; returns its JSON result or None.

animate

Handle an animation request (set / start / cancel) for one view.

call

Call a native module method with a {"call_id", "args"} envelope.

fire

Emit a view event as native would.

emit_module_event

Push an unsolicited module event (AppState change, ...) into Python.

resolve_pending

Settle a call that returned pending.

host_event

Deliver a screen lifecycle event as the native host would.

complete_animation

Report a native animation as finished (or interrupted) to Python.

find

Every live view whose type is type_name, in creation order.

roots

Views with no parent (the attached screen roots and detached subtrees).

pending

Sentinel a module handler returns to answer pending.

protocol_version

protocol_version() -> int

Return the protocol version compiled into the native library.

set_callback

set_callback(
    callback: Callable[[str, int, str, str], None],
) -> None

Install callback as the native -> Python entry point.

apply

apply(transaction_json: str) -> str

Validate a full revision before applying any native mutations.

measure

measure(
    tag: int, max_width: float, max_height: float
) -> Tuple[float, float]

Return the intrinsic (width, height) of the view tag under the constraints.

command

command(
    tag: int, name: str, args_json: str
) -> Optional[str]

Run an imperative command on one view; returns its JSON result or None.

animate

animate(tag: int, request_json: str) -> Optional[str]

Handle an animation request (set / start / cancel) for one view.

call

call(
    module: str, method: str, args_json: str
) -> Optional[str]

Call a native module method with a {"call_id", "args"} envelope.

fire

fire(tag: int, name: str, *args: Any) -> None

Emit a view event as native would.

emit_module_event

emit_module_event(
    module: str, event: str, payload: Any = None
) -> None

Push an unsolicited module event (AppState change, ...) into Python.

resolve_pending

resolve_pending(
    call_id: int,
    value: Any = None,
    *,
    error: Optional[str] = None
) -> None

Settle a call that returned pending.

host_event

host_event(
    screen: int, event: str, payload: Any = None
) -> None

Deliver a screen lifecycle event as the native host would.

complete_animation

complete_animation(
    anim_id: int, finished: bool = True
) -> None

Report a native animation as finished (or interrupted) to Python.

find

find(type_name: str) -> List[FakeNativeView]

Every live view whose type is type_name, in creation order.

roots

roots() -> List[FakeNativeView]

Views with no parent (the attached screen roots and detached subtrees).

pending staticmethod

pending() -> object

Sentinel a module handler returns to answer pending.

Bootstrap

Entry point the native app templates run right after Python starts.

Both templates call start once the interpreter is up, on a background thread at launch:

pythonnative.bootstrap.start(dev, True, "app.main")

It connects the two halves of the bridge (installing the native -> Python callback on iOS), verifies the protocol version, routes print() to the console on iOS, warms the asyncio runtime, in debug builds starts the dev client that syncs sources from pn start, and imports the entry module, so the first screen only has to render. From then on the native runtime drives everything through callback("host", ...); see docs/concepts/bridge.md.

Functions:

Name Description
start

Connect the bridge and prepare the runtime.

status

Return the result of the last start (empty before it ran).

start

start(
    dev: bool = False,
    strict: bool = False,
    entry: Optional[str] = None,
) -> Dict[str, Any]

Connect the bridge and prepare the runtime.

Parameters:

Name Type Description Default
dev bool

Enable dev mode (RedBox, validation warnings, the dev client). Debug templates pass True.

False
strict bool

Re-raise the failure after recording it. The templates pass True so a broken bridge surfaces as a bootstrap error screen with the full traceback.

False
entry Optional[str]

The app's entry module, imported last so a broken app fails here with its traceback, and so the first screen doesn't pay for the import.

None

Returns:

Type Description
Dict[str, Any]

A status dict ({"protocol": 2, "platform": "ios"}) that the

Dict[str, Any]

template logs. Unless strict is set this never raises:

Dict[str, Any]

failures are printed and reported in the dict under

Dict[str, Any]

"error".

status

status() -> Dict[str, Any]

Return the result of the last start (empty before it ran).

Next steps