Dev server and dev client¶
The development loop is a server process (pn start) and any number of
clients: the browser preview, PythonNative Go, and debug builds on
simulators, emulators, and devices. See the Development workflow
guide for how they fit together; this page documents the modules.
pythonnative.devserver¶
The PythonNative dev server: one process that serves every dev client.
pn start (and pn preview, which is pn start plus a browser
tab) runs a DevServer. It watches
the project's app/ directory, keeps a content-addressed manifest of
the sources, and speaks a small JSON protocol over WebSocket to two
kinds of peers:
- Dev clients: debug builds of the app running on a simulator,
emulator, or physical device. They sync sources into an on-device
overlay, Fast Refresh when files change, and stream their logs and
errors back to the terminal. See
pythonnative.devclient. - The browser preview: a page served by this server that renders
the app through the bridge protocol, exactly as the Swift and Kotlin
runtimes do. See
pythonnative.bridge.web.
Everything here is standard library only (asyncio streams plus a
hand-rolled RFC 6455 implementation in pythonnative.devserver.ws)
so the same
code also runs inside the embedded interpreter on device.
The server only answers peers that present the per-user dev token; see
pythonnative.devserver.auth.
Modules:
| Name | Description |
|---|---|
auth |
The per-user development token that guards the dev server. |
compat |
Decide whether a dev client can run the project |
editor |
Open a source location from DevTools or an error screen in the developer's editor. |
hub |
Route DevTools and debugger traffic between pages, the preview, and dev clients. |
packages |
Sync pure-Python packages from the developer's environment to dev clients. |
qr |
A small, dependency-free QR code encoder for the dev server. |
server |
The dev server: HTTP for static assets, WebSocket for live peers. |
watcher |
Source snapshots and change detection for the dev server. |
ws |
A small RFC 6455 (WebSocket) implementation on the standard library. |
Classes:
| Name | Description |
|---|---|
DevServer |
Serve sources, assets, and the live protocol for one project. |
ServerInfo |
What the server is serving and where. |
FileWatcher |
Poll a source tree and report changes on a background thread. |
SourceSnapshot |
The state of a source tree at one instant. |
Functions:
| Name | Description |
|---|---|
load_token |
Return this user's dev token, creating it on first use. |
lan_addresses |
Best-effort list of this machine's non-loopback IPv4 addresses. |
snapshot_sources |
Hash every synced file under |
Attributes:
| Name | Type | Description |
|---|---|---|
DEFAULT_PORT |
Default port for |
DevServer
¶
DevServer(
project_root: str,
entry_module: str = "app.main",
*,
host: str = "0.0.0.0",
port: int = DEFAULT_PORT,
project_name: str = "",
token: Optional[str] = None,
static_dir: Optional[str] = None,
log: Optional[Logger] = None,
watch: bool = True,
config: Any = None,
debug_port: Optional[int] = None,
open_devtools: Optional[Callable[[], None]] = None
)
Serve sources, assets, and the live protocol for one project.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
project_root
|
str
|
Directory containing |
required |
entry_module
|
str
|
The app's entry module ( |
'app.main'
|
host
|
str
|
Bind address. |
'0.0.0.0'
|
port
|
int
|
TCP port; |
DEFAULT_PORT
|
project_name
|
str
|
Shown in the preview page and |
''
|
token
|
Optional[str]
|
The dev token clients must present; defaults to this
user's token from
|
None
|
static_dir
|
Optional[str]
|
Where the preview page's assets live. |
None
|
log
|
Optional[Logger]
|
Where to print client logs and connection events. |
None
|
watch
|
bool
|
Whether to run the file watcher. |
True
|
config
|
Any
|
The project's |
None
|
debug_port
|
Optional[int]
|
Port of the debugger proxy on |
None
|
open_devtools
|
Optional[Callable[[], None]]
|
Called when an app's dev menu asks to open DevTools. |
None
|
Methods:
| Name | Description |
|---|---|
start |
Bind the socket on a background thread and return the address. |
stop |
Close every connection and stop the thread. |
set_preview_channel |
Install the handler for the browser preview's bridge traffic. |
add_change_listener |
Be told (on the watcher thread) when sources change; returns an unsubscribe. |
app_scheme |
The connect scheme of the project's own debug build, when the config names an app id. |
connect_links |
Connect links for PythonNative Go and the project's own debug build. |
preview_target |
Register the browser preview's in-process agent as a DevTools target (any thread). |
publish_local |
Publish an event for an in-process target (any thread). |
broadcast_command |
Send |
manifest |
The |
status |
The |
Attributes:
| Name | Type | Description |
|---|---|---|
snapshot |
SourceSnapshot
|
The current source snapshot. |
clients |
List[DevClient]
|
Connected dev clients (a copy). |
project_runtime |
Any
|
What the project needs from a dev client (computed once, on first use). |
project_runtime
property
¶
project_runtime: Any
What the project needs from a dev client (computed once, on first use).
start
¶
start() -> ServerInfo
Bind the socket on a background thread and return the address.
Raises:
| Type | Description |
|---|---|
OSError
|
When the port is taken (the CLI prints a hint). |
set_preview_channel
¶
set_preview_channel(
channel: Optional[PreviewChannel],
) -> None
Install the handler for the browser preview's bridge traffic.
add_change_listener
¶
add_change_listener(
listener: Callable[
[SourceChange, SourceSnapshot], None
],
) -> Callable[[], None]
Be told (on the watcher thread) when sources change; returns an unsubscribe.
app_scheme
¶
The connect scheme of the project's own debug build, when the config names an app id.
connect_links
¶
Connect links for PythonNative Go and the project's own debug build.
The preferred link is Go's when the project can run in it.
preview_target
¶
preview_target(agent: Any) -> None
Register the browser preview's in-process agent as a DevTools target (any thread).
publish_local
¶
Publish an event for an in-process target (any thread).
ServerInfo
dataclass
¶
ServerInfo(
host: str,
port: int,
project_root: str,
entry_module: str,
project_name: str = "",
token: str = "",
)
What the server is serving and where.
Methods:
| Name | Description |
|---|---|
url |
The HTTP base URL, substituting |
preview_url |
The browser preview URL, carrying the dev token once. |
ws_url |
The dev-client WebSocket URL, including the dev token. |
devtools_url |
The DevTools page URL, carrying the dev token once. |
connect_url |
The server URL a connect link carries ( |
Attributes:
| Name | Type | Description |
|---|---|---|
display_host |
str
|
A host suitable for a URL ( |
FileWatcher
¶
FileWatcher(
root: str,
on_change: Callable[
[SourceChange, SourceSnapshot], None
],
*,
subdirs: Sequence[str] = ("app",),
interval: float = 0.25,
settle: float = 0.08
)
Poll a source tree and report changes on a background thread.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
root
|
str
|
Project root. |
required |
on_change
|
Callable[[SourceChange, SourceSnapshot], None]
|
Called with a
|
required |
subdirs
|
Sequence[str]
|
Directories under |
('app',)
|
interval
|
float
|
Seconds between polls. |
0.25
|
settle
|
float
|
Seconds a change must be stable before it is reported, so an editor's write-then-rename or a multi-file save lands as one reload instead of several. |
0.08
|
Methods:
| Name | Description |
|---|---|
start |
Start polling on a daemon thread (idempotent). |
stop |
Stop polling and join the thread. |
poll |
Scan once and return the change since the last scan (or |
Attributes:
| Name | Type | Description |
|---|---|---|
snapshot |
SourceSnapshot
|
The most recent snapshot. |
poll
¶
poll() -> Optional[SourceChange]
Scan once and return the change since the last scan (or None).
SourceSnapshot
dataclass
¶
SourceSnapshot(
root: str,
files: Dict[str, str] = dict(),
mtimes: Dict[str, Tuple[int, int]] = dict(),
version: str = "",
)
The state of a source tree at one instant.
Attributes:
| Name | Type | Description |
|---|---|---|
root |
str
|
Absolute directory the relative paths are resolved against
(the project root; paths start with |
files |
Dict[str, str]
|
|
mtimes |
Dict[str, Tuple[int, int]]
|
|
version |
str
|
A digest of the whole tree, stable across processes for identical contents. |
Methods:
| Name | Description |
|---|---|
read |
Return the current bytes of |
diff |
Describe how to get from |
load_token
¶
load_token() -> str
Return this user's dev token, creating it on first use.
PN_DEV_TOKEN wins when set. Otherwise the token file is read, or
created with mode 0600 (its directory with 0700) holding
secrets.token_urlsafe(24). A file that other users can read is
tightened to 0600; an empty one is replaced.
lan_addresses
¶
Best-effort list of this machine's non-loopback IPv4 addresses.
Used to print a URL a physical device on the same Wi-Fi can reach.
snapshot_sources
¶
snapshot_sources(
root: str,
subdirs: Sequence[str] = ("app",),
*,
previous: Optional[SourceSnapshot] = None
) -> SourceSnapshot
Hash every synced file under root/<subdir> for each of subdirs.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
root
|
str
|
Project root. |
required |
subdirs
|
Sequence[str]
|
Directories (relative to |
('app',)
|
previous
|
Optional[SourceSnapshot]
|
An earlier snapshot; files whose |
None
|
Server¶
The dev server: HTTP for static assets, WebSocket for live peers.
One DevServer runs on its
own thread with a private asyncio loop, so it works both inside
pn start and in tests independently of the Python application
loop. It exposes:
GET /: the browser preview page. With a valid?token=, the response instead sets thepn_tokencookie and redirects to/, so the token doesn't stay in the address bar.GET /static/<name>: preview assets (JS, CSS).GET /manifest:{"version", "entry", "files": {path: sha256}}.GET /file/<path>: raw bytes of one synced source file.GET /assets/<path>: a file underapp/assets/;/assets/pn_assets.jsonis the asset manifest and/assets/fonts.cssdeclares the bundled fonts for the browser preview.GET /status: server, project, and connected-peer information.GET /devtools: the DevTools page (/devtools/<name>for its assets), authenticated like/.GET /connect: connect links, server URLs, and their QR codes.WS /ws?role=client: the dev-client protocol (see below).WS /ws?role=preview: the browser preview's bridge channel; the server only relays text frames between the page and thePreviewChannelhandler installed by the preview.
Every route except the page and /static/ requires the per-user dev
token (see pythonnative.devserver.auth) as a token query
parameter, an X-PN-Token header, or the pn_token cookie.
Requests without it get 401; requests with a wrong one get 403.
A WebSocket upgrade that carries an Origin header (every browser
sends one) must also come from the server's own origin: the Origin
host and port must equal the Host header. Native dev clients send no
Origin.
Dev session protocol, version 2 (JSON objects, one per text frame; see
RFC 0006 and pythonnative.devclient):
- client -> server
hello:{"type": "hello", "session": 2, "client": "go" | "app", "platform", "device", "app", "runtime": {...}, "files": {path: sha256}, "packages": {name: version}}. - server -> client
incompatible(reason,fix) when the client can't run the project (seepythonnative.devserver.compat), orsync:{"type": "sync", "version", "entry", "project", "files": [{"path", "sha256", "content", "encoding"}], "removed": [...]}followed bypackagesfor missing pure-Python distributions. Thesyncshape is sent as"update"whenever the watcher sees a change. - server -> client
rpc(id,method,params),tunnel,menu, andreload; client -> serverrpc_result,event(topic,data),tunnel, andpackages_ready. - client -> server
log(level,text),error(phase,text),reloaded(version,mode,modules): streamed to the terminal and DevTools.
WS /ws?role=devtools carries DevTools pages (see
pythonnative.devserver.hub), and a separate listener on
127.0.0.1:5678 accepts Debug Adapter Protocol connections.
Classes:
| Name | Description |
|---|---|
ServerInfo |
What the server is serving and where. |
PreviewPeer |
A connected browser preview page; |
PreviewChannel |
What the preview installs to receive the page's bridge traffic. |
DevServer |
Serve sources, assets, and the live protocol for one project. |
Functions:
| Name | Description |
|---|---|
lan_addresses |
Best-effort list of this machine's non-loopback IPv4 addresses. |
Attributes:
| Name | Type | Description |
|---|---|---|
DEFAULT_PORT |
Default port for |
|
DEFAULT_DEBUG_PORT |
Default port of the debugger proxy ( |
|
GO_APP_ID |
PythonNative Go's application id; its connect scheme is |
DEFAULT_DEBUG_PORT
module-attribute
¶
Default port of the debugger proxy (debugpy's conventional port).
GO_APP_ID
module-attribute
¶
PythonNative Go's application id; its connect scheme is pn-com.pythonnative.go.
ServerInfo
dataclass
¶
ServerInfo(
host: str,
port: int,
project_root: str,
entry_module: str,
project_name: str = "",
token: str = "",
)
What the server is serving and where.
Methods:
| Name | Description |
|---|---|
url |
The HTTP base URL, substituting |
preview_url |
The browser preview URL, carrying the dev token once. |
ws_url |
The dev-client WebSocket URL, including the dev token. |
devtools_url |
The DevTools page URL, carrying the dev token once. |
connect_url |
The server URL a connect link carries ( |
Attributes:
| Name | Type | Description |
|---|---|---|
display_host |
str
|
A host suitable for a URL ( |
PreviewPeer
¶
PreviewPeer(server: 'DevServer', writer: StreamWriter)
PreviewChannel
¶
Bases: Protocol
What the preview installs to receive the page's bridge traffic.
Every method is called on the server thread; implementations hop to their own thread as needed.
Methods:
| Name | Description |
|---|---|
on_preview_connected |
A page connected ( |
on_preview_message |
A text frame arrived from the page. |
on_preview_disconnected |
The page went away. |
on_preview_connected
¶
on_preview_connected(
peer: PreviewPeer, info: Dict[str, Any]
) -> None
A page connected (info carries its query parameters).
on_preview_message
¶
on_preview_message(peer: PreviewPeer, text: str) -> None
A text frame arrived from the page.
DevClient
dataclass
¶
DevClient(
id: int,
writer: StreamWriter,
platform: str = "unknown",
device: str = "",
app: str = "",
kind: str = "app",
connected_at: float = time(),
files: Dict[str, str] = dict(),
target: Optional[ClientTarget] = None,
synced: bool = False,
)
DevToolsPeer
¶
DevToolsPeer(writer: StreamWriter)
DevServer
¶
DevServer(
project_root: str,
entry_module: str = "app.main",
*,
host: str = "0.0.0.0",
port: int = DEFAULT_PORT,
project_name: str = "",
token: Optional[str] = None,
static_dir: Optional[str] = None,
log: Optional[Logger] = None,
watch: bool = True,
config: Any = None,
debug_port: Optional[int] = None,
open_devtools: Optional[Callable[[], None]] = None
)
Serve sources, assets, and the live protocol for one project.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
project_root
|
str
|
Directory containing |
required |
entry_module
|
str
|
The app's entry module ( |
'app.main'
|
host
|
str
|
Bind address. |
'0.0.0.0'
|
port
|
int
|
TCP port; |
DEFAULT_PORT
|
project_name
|
str
|
Shown in the preview page and |
''
|
token
|
Optional[str]
|
The dev token clients must present; defaults to this
user's token from
|
None
|
static_dir
|
Optional[str]
|
Where the preview page's assets live. |
None
|
log
|
Optional[Logger]
|
Where to print client logs and connection events. |
None
|
watch
|
bool
|
Whether to run the file watcher. |
True
|
config
|
Any
|
The project's |
None
|
debug_port
|
Optional[int]
|
Port of the debugger proxy on |
None
|
open_devtools
|
Optional[Callable[[], None]]
|
Called when an app's dev menu asks to open DevTools. |
None
|
Methods:
| Name | Description |
|---|---|
start |
Bind the socket on a background thread and return the address. |
stop |
Close every connection and stop the thread. |
set_preview_channel |
Install the handler for the browser preview's bridge traffic. |
add_change_listener |
Be told (on the watcher thread) when sources change; returns an unsubscribe. |
app_scheme |
The connect scheme of the project's own debug build, when the config names an app id. |
connect_links |
Connect links for PythonNative Go and the project's own debug build. |
preview_target |
Register the browser preview's in-process agent as a DevTools target (any thread). |
publish_local |
Publish an event for an in-process target (any thread). |
broadcast_command |
Send |
manifest |
The |
status |
The |
Attributes:
| Name | Type | Description |
|---|---|---|
snapshot |
SourceSnapshot
|
The current source snapshot. |
clients |
List[DevClient]
|
Connected dev clients (a copy). |
project_runtime |
Any
|
What the project needs from a dev client (computed once, on first use). |
project_runtime
property
¶
project_runtime: Any
What the project needs from a dev client (computed once, on first use).
start
¶
start() -> ServerInfo
Bind the socket on a background thread and return the address.
Raises:
| Type | Description |
|---|---|
OSError
|
When the port is taken (the CLI prints a hint). |
set_preview_channel
¶
set_preview_channel(
channel: Optional[PreviewChannel],
) -> None
Install the handler for the browser preview's bridge traffic.
add_change_listener
¶
add_change_listener(
listener: Callable[
[SourceChange, SourceSnapshot], None
],
) -> Callable[[], None]
Be told (on the watcher thread) when sources change; returns an unsubscribe.
app_scheme
¶
The connect scheme of the project's own debug build, when the config names an app id.
connect_links
¶
Connect links for PythonNative Go and the project's own debug build.
The preferred link is Go's when the project can run in it.
preview_target
¶
preview_target(agent: Any) -> None
Register the browser preview's in-process agent as a DevTools target (any thread).
publish_local
¶
Publish an event for an in-process target (any thread).
Authentication¶
The per-user development token that guards the dev server.
pn start serves the application's source code, so everything that
reads it has to prove it belongs to the developer running the server.
The proof is a random token generated once per user and stored with mode
0600 in ~/.pythonnative/dev-token. It's reused across restarts, so
debug builds that pn run launched keep connecting after the server
restarts. Delete the file to issue a new token; every client then needs
the new URL.
Clients present the token in one of three ways (see
request_token):
- the
tokenquery parameter (pn runbakes it into the dev-client URL, andpn startprints it in the preview URL), - the
X-PN-Tokenheader, or - the
pn_tokencookie, which the browser preview receives once in exchange for the query parameter.
Two environment variables override the stored token:
PN_DEV_TOKEN supplies the token itself, and PN_DEV_TOKEN_FILE
points at a different token file.
Functions:
| Name | Description |
|---|---|
token_path |
Where the token lives: |
token_source |
Where |
load_token |
Return this user's dev token, creating it on first use. |
token_matches |
Compare |
request_token |
The token a request presents, if any. |
with_token |
Return |
Attributes:
| Name | Type | Description |
|---|---|---|
TOKEN_ENV |
Environment variable that supplies the token directly (skips the file). |
|
TOKEN_FILE_ENV |
Environment variable naming the token file (default |
|
QUERY_PARAM |
Query parameter that carries the token in URLs. |
|
HEADER_NAME |
Request header that carries the token. |
|
COOKIE_NAME |
Cookie the browser preview holds after exchanging the query parameter. |
TOKEN_ENV
module-attribute
¶
Environment variable that supplies the token directly (skips the file).
TOKEN_FILE_ENV
module-attribute
¶
Environment variable naming the token file (default ~/.pythonnative/dev-token).
QUERY_PARAM
module-attribute
¶
Query parameter that carries the token in URLs.
COOKIE_NAME
module-attribute
¶
Cookie the browser preview holds after exchanging the query parameter.
token_path
¶
token_path() -> Path
Where the token lives: $PN_DEV_TOKEN_FILE or ~/.pythonnative/dev-token.
load_token
¶
load_token() -> str
Return this user's dev token, creating it on first use.
PN_DEV_TOKEN wins when set. Otherwise the token file is read, or
created with mode 0600 (its directory with 0700) holding
secrets.token_urlsafe(24). A file that other users can read is
tightened to 0600; an empty one is replaced.
token_matches
¶
Compare candidate against token in constant time.
request_token
¶
The token a request presents, if any.
Checks the token query parameter, then the X-PN-Token header,
then the pn_token cookie. headers must have lower-cased names.
File watcher and source snapshots¶
Source snapshots and change detection for the dev server.
The dev server treats the project's app/ directory as a content-
addressed tree: every dev client gets the same manifest (relative path
to SHA-256), so a client can tell exactly which files it is missing
after a reconnect, and a change notification can carry the new bytes.
FileWatcher polls the
tree with os.stat rather than a native file-system event API so it
behaves identically on every host OS without a dependency; the poll
interval is short enough that saves feel instant.
Classes:
| Name | Description |
|---|---|
SourceSnapshot |
The state of a source tree at one instant. |
SourceChange |
Paths that changed (added or modified) and paths that disappeared. |
FileWatcher |
Poll a source tree and report changes on a background thread. |
Functions:
| Name | Description |
|---|---|
is_synced_file |
Whether a file under |
snapshot_sources |
Hash every synced file under |
MAX_SYNC_FILE_BYTES
module-attribute
¶
Files larger than this are left out of the sync set (they belong in a real build).
SourceSnapshot
dataclass
¶
SourceSnapshot(
root: str,
files: Dict[str, str] = dict(),
mtimes: Dict[str, Tuple[int, int]] = dict(),
version: str = "",
)
The state of a source tree at one instant.
Attributes:
| Name | Type | Description |
|---|---|---|
root |
str
|
Absolute directory the relative paths are resolved against
(the project root; paths start with |
files |
Dict[str, str]
|
|
mtimes |
Dict[str, Tuple[int, int]]
|
|
version |
str
|
A digest of the whole tree, stable across processes for identical contents. |
Methods:
| Name | Description |
|---|---|
read |
Return the current bytes of |
diff |
Describe how to get from |
SourceChange
dataclass
¶
Paths that changed (added or modified) and paths that disappeared.
FileWatcher
¶
FileWatcher(
root: str,
on_change: Callable[
[SourceChange, SourceSnapshot], None
],
*,
subdirs: Sequence[str] = ("app",),
interval: float = 0.25,
settle: float = 0.08
)
Poll a source tree and report changes on a background thread.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
root
|
str
|
Project root. |
required |
on_change
|
Callable[[SourceChange, SourceSnapshot], None]
|
Called with a
|
required |
subdirs
|
Sequence[str]
|
Directories under |
('app',)
|
interval
|
float
|
Seconds between polls. |
0.25
|
settle
|
float
|
Seconds a change must be stable before it is reported, so an editor's write-then-rename or a multi-file save lands as one reload instead of several. |
0.08
|
Methods:
| Name | Description |
|---|---|
start |
Start polling on a daemon thread (idempotent). |
stop |
Stop polling and join the thread. |
poll |
Scan once and return the change since the last scan (or |
Attributes:
| Name | Type | Description |
|---|---|---|
snapshot |
SourceSnapshot
|
The most recent snapshot. |
poll
¶
poll() -> Optional[SourceChange]
Scan once and return the change since the last scan (or None).
is_synced_file
¶
Whether a file under app/ takes part in dev sync.
Editor swap files, byte-code caches, and VCS metadata are skipped; everything else (Python modules and data files an app reads at runtime) is synced so the on-device overlay mirrors the source tree.
snapshot_sources
¶
snapshot_sources(
root: str,
subdirs: Sequence[str] = ("app",),
*,
previous: Optional[SourceSnapshot] = None
) -> SourceSnapshot
Hash every synced file under root/<subdir> for each of subdirs.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
root
|
str
|
Project root. |
required |
subdirs
|
Sequence[str]
|
Directories (relative to |
('app',)
|
previous
|
Optional[SourceSnapshot]
|
An earlier snapshot; files whose |
None
|
DevTools routing¶
Route DevTools and debugger traffic between pages, the preview, and dev clients.
The dev server's Hub keeps one
Target per running app: the browser
preview (whose agent lives in the pn start process) and every
connected dev client. DevTools pages pick a target and send it requests;
targets publish events, which the hub keeps a short history of and
broadcasts to every page. Debugger connections are joined to the
selected target's debug adapter.
Everything here runs on the dev server's own event loop.
Classes:
| Name | Description |
|---|---|
Target |
A running app DevTools can talk to. |
LocalTarget |
The browser preview: its agent runs in this process. |
ClientTarget |
A connected dev client (PythonNative Go or a debug build). |
Hub |
Targets, DevTools pages, event history, and debugger routing. |
Page
¶
Target
¶
A running app DevTools can talk to.
Methods:
| Name | Description |
|---|---|
label |
|
describe |
The target's entry in the DevTools target list. |
call |
Run devtools request |
open_debug_stream |
Start the app's debug adapter and connect to it. |
DebugStream
¶
One debugger connection's path to an app's adapter.
LocalTarget
¶
LocalTarget(agent: Any, loop: AbstractEventLoop)
Bases: Target
The browser preview: its agent runs in this process.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
agent
|
Any
|
The preview's |
required |
loop
|
AbstractEventLoop
|
The dev server's loop, where replies are delivered. |
required |
Methods:
| Name | Description |
|---|---|
label |
Always |
call |
Dispatch to the in-process agent and wait for its reply on this loop. |
open_debug_stream |
Start the preview's adapter and connect to it directly. |
call
async
¶
Dispatch to the in-process agent and wait for its reply on this loop.
open_debug_stream
async
¶
open_debug_stream(hub: 'Hub') -> DebugStream
Start the preview's adapter and connect to it directly.
ClientTarget
¶
Bases: Target
A connected dev client (PythonNative Go or a debug build).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
client_id
|
int
|
The server's connection id. |
required |
send
|
Callable[[Dict[str, Any]], None]
|
Queues one JSON message to the client. |
required |
Methods:
| Name | Description |
|---|---|
call |
Send an |
settle |
Deliver an |
packages_ready |
A |
ensure_packages |
Sync |
open_debug_stream |
Sync |
tunnel |
Deliver a |
disconnected |
Fail pending requests and close streams. |
call
async
¶
Send an rpc to the client and wait for its rpc_result.
packages_ready
¶
A packages message with a request id finished installing.
ensure_packages
async
¶
Sync names (and their dependencies) to the client and wait until they're installed.
open_debug_stream
async
¶
open_debug_stream(hub: 'Hub') -> DebugStream
Sync debugpy, start the client's adapter, and tunnel to it.
Hub
¶
Hub(
log: Logger,
on_open_devtools: Optional[Callable[[], None]] = None,
on_open_editor: Optional[
Callable[[str, int], Any]
] = None,
)
Targets, DevTools pages, event history, and debugger routing.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
log
|
Logger
|
Terminal logger. |
required |
on_open_devtools
|
Optional[Callable[[], None]]
|
Called when an app asks to open DevTools. |
None
|
Methods:
| Name | Description |
|---|---|
add_target |
Register a target and tell every page. |
remove_target |
Forget a target and tell every page. |
describe_targets |
Every target, most recently connected first. |
broadcast_targets |
Send the target list to every page. |
default_target |
The debug target: the chosen one, else the newest device, else the preview. |
on_event |
Record and broadcast an event a target published. |
log_line |
Record one log line from a target. |
add_page |
Register a DevTools page; it receives the targets and history. |
remove_page |
Forget a page. |
broadcast |
Send |
page_request |
Answer one |
clear |
Drop a topic's history ( |
debug_connection |
Join a Debug Adapter Protocol connection to the selected target's adapter. |
describe_targets
¶
Every target, most recently connected first.
default_target
¶
The debug target: the chosen one, else the newest device, else the preview.
on_event
¶
Record and broadcast an event a target published.
log_line
¶
Record one log line from a target.
add_page
¶
add_page(page: Page) -> None
Register a DevTools page; it receives the targets and history.
page_request
async
¶
Answer one rpc message from a page.
clear
¶
Drop a topic's history (target_id=None for every target).
debug_connection
async
¶
debug_connection(
reader: StreamReader, writer: StreamWriter
) -> None
Join a Debug Adapter Protocol connection to the selected target's adapter.
Compatibility checks¶
Decide whether a dev client can run the project pn start serves.
A dev client syncs Python sources, never native code, so the code it was built with has to match the project's:
- PythonNative Go carries the framework and every built-in native
module, nothing else. It runs a project when its framework version
equals the host's (the bundled
pythonnativepackage can't be replaced while it runs), the project adds no native plugins, and every package the project needs is pure Python. - A project's debug build carries the framework and plugins it was
built with. The framework version and the plugin set must still match;
otherwise
pn runmust rebuild it.
check returns an
Incompatibility with a
reason and the command that fixes it, which the server sends instead of a
sync and the device shows.
Classes:
| Name | Description |
|---|---|
Incompatibility |
Why a client can't run the project, and how to fix it. |
ProjectRuntime |
What the project needs from a dev client. |
Functions:
| Name | Description |
|---|---|
project_runtime |
Describe what the project at |
check |
Return why the client that sent |
Incompatibility
dataclass
¶
Why a client can't run the project, and how to fix it.
Methods:
| Name | Description |
|---|---|
to_message |
The |
ProjectRuntime
dataclass
¶
ProjectRuntime(
pythonnative: str,
protocol: int,
plugins: List[str] = list(),
requirements: List[str] = list(),
native_packages: List[str] = list(),
missing_packages: List[str] = list(),
)
What the project needs from a dev client.
Attributes:
| Name | Type | Description |
|---|---|---|
pythonnative |
str
|
The host's framework version. |
protocol |
int
|
The host's bridge protocol version. |
plugins |
List[str]
|
Native plugin names the project uses. |
requirements |
List[str]
|
|
native_packages |
List[str]
|
Required distributions with compiled code. |
missing_packages |
List[str]
|
Requirements that aren't installed on the host. |
project_runtime
¶
project_runtime(config: Any = None) -> ProjectRuntime
Describe what the project at config (an AppConfig, or None) needs.
check
¶
check(
hello: Dict[str, Any], project: ProjectRuntime
) -> Optional[Incompatibility]
Return why the client that sent hello can't run project, or None.
Package sync¶
Sync pure-Python packages from the developer's environment to dev clients.
PythonNative Go bundles no third-party packages, and a project's own
debug build bundles the wheels its [requirements] named when it was
built. The dev server fills the gap from the environment pn start runs
in, which the browser preview already needs: it resolves [requirements]
(and their dependencies) against the installed distributions and sends a
client every pure-Python distribution its bundle lacks. The client writes
them into its overlay's site-packages.
A distribution with compiled extension modules can't be synced: Go can't
run that project, and a debug build must be rebuilt to include it.
debugpy is the exception: its compiled modules are optional speedups,
so the debugger syncs its Python sources alone.
Classes:
| Name | Description |
|---|---|
HostDistribution |
One installed distribution, as the dev server would sync it. |
Functions:
| Name | Description |
|---|---|
requirement_name |
The distribution a |
find_distribution |
The installed distribution called |
distribution_files |
Classify a distribution's files for syncing. |
resolve |
Resolve |
Attributes:
| Name | Type | Description |
|---|---|---|
BINARY_SUFFIXES |
File suffixes that make a distribution native-only. |
BINARY_SUFFIXES
module-attribute
¶
File suffixes that make a distribution native-only.
HostDistribution
dataclass
¶
HostDistribution(
name: str,
version: str,
files: List[Tuple[str, Path]] = list(),
binary: List[str] = list(),
site: Optional[Path] = None,
)
One installed distribution, as the dev server would sync it.
Attributes:
| Name | Type | Description |
|---|---|---|
name |
str
|
The distribution's name. |
version |
str
|
Its installed version. |
files |
List[Tuple[str, Path]]
|
|
binary |
List[str]
|
Compiled files that make it native-only. |
site |
Optional[Path]
|
The |
Methods:
| Name | Description |
|---|---|
size |
Total bytes the sync would send. |
requirement_extras
¶
The extras a requirement asks for: httpx[http2,socks] gives {"http2", "socks"}.
requirement_name
¶
The distribution a [requirements] entry or Requires-Dist line names.
find_distribution
¶
find_distribution(name: str) -> Optional[Distribution]
The installed distribution called name (any normalization), or None.
distribution_files
¶
distribution_files(
dist: Distribution, *, sources_only: bool = False
) -> HostDistribution
Classify a distribution's files for syncing.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
dist
|
Distribution
|
An installed distribution. |
required |
sources_only
|
bool
|
Drop compiled files instead of reporting them
( |
False
|
resolve
¶
resolve(
requirements: Sequence[str], *, skip: Iterable[str] = ()
) -> Tuple[List[HostDistribution], List[str]]
Resolve requirements and their dependencies in this environment.
Requested extras (httpx[http2]) bring in their dependencies.
Dependencies that aren't installed are assumed to be excluded by an
environment marker and are skipped.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
requirements
|
Sequence[str]
|
|
required |
skip
|
Iterable[str]
|
Distribution names the client already bundles, and
|
()
|
Returns:
| Type | Description |
|---|---|
List[HostDistribution]
|
|
List[str]
|
|
Editor links¶
Open a source location from DevTools or an error screen in the developer's editor.
Frames reported by dev clients use project-relative paths (app/main.py)
for application code, because the device runs a copy of it. The dev
server resolves them against the project root, and synced packages
against the environment it runs in, then opens the file with:
$PN_EDITOR, a command template where{file}and{line}are substituted ("code --goto {file}:{line}","subl {file}:{line}"); without placeholders,{file}:{line}is appended;- VS Code's
code --gotowhen it's onPATH; - the system's default handler for the file.
Functions:
| Name | Description |
|---|---|
resolve_source |
Map a reported frame path to a file on this machine, or |
editor_command |
The command that opens |
open_in_editor |
Open |
QR codes¶
A small, dependency-free QR code encoder for the dev server.
pn start prints a QR code for the dev-client connect link
(pn-com.pythonnative.go://connect?url=...) so a phone running the
dev client can join the server by pointing its camera at the terminal,
and the DevTools page shows the same link as an SVG. Both renderings come
from this module, which is standard library only so it also runs inside
the embedded interpreter on device.
The encoder implements QR Code Model 2 as specified in ISO/IEC 18004:
- numeric, alphanumeric, and byte (UTF-8) modes, picked automatically as the most compact single mode that holds the whole input,
- error correction levels
L,M,Q, andH, - versions 1 through 40, choosing the smallest version that fits,
- Reed-Solomon error correction with the standard block interleaving, BCH-protected format and version information, and
- all eight data masks, keeping the one with the lowest penalty score.
Example
Classes:
| Name | Description |
|---|---|
QRCode |
An encoded QR code symbol. |
Functions:
| Name | Description |
|---|---|
encode |
Encode |
to_terminal |
Render a QR code as Unicode half blocks for a terminal. |
to_svg |
Render a QR code as a compact SVG document. |
QRCode
dataclass
¶
QRCode(
version: int,
size: int,
modules: Tuple[Tuple[bool, ...], ...],
error: str = "M",
mask: int = 0,
)
An encoded QR code symbol.
Attributes:
| Name | Type | Description |
|---|---|---|
version |
int
|
The symbol version, from 1 (21 x 21 modules) to 40 (177 x 177 modules). |
size |
int
|
The number of modules along each side, |
modules |
Tuple[Tuple[bool, ...], ...]
|
The module grid as rows from top to bottom, each row
listing modules from left to right. |
error |
str
|
The error correction level, one of |
mask |
int
|
The data mask pattern applied to the symbol, from 0 to 7. |
encode
¶
encode(
text: str,
*,
error: str = "M",
version: Optional[int] = None,
mask: Optional[int] = None
) -> QRCode
Encode text as a QR code.
The encoder uses numeric mode when text is all digits,
alphanumeric mode when it only uses the 45-character QR alphabet
(digits, uppercase letters, space, and $%*+-./:), and byte mode
with UTF-8 otherwise. No ECI header is written; scanners detect UTF-8
on their own.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
text
|
str
|
The text to encode. |
required |
error
|
str
|
The error correction level: |
'M'
|
version
|
Optional[int]
|
Force a specific version from 1 to 40 instead of the smallest one that fits. |
None
|
mask
|
Optional[int]
|
Force a data mask pattern from 0 to 7 instead of the one with the lowest penalty score. |
None
|
Returns:
| Type | Description |
|---|---|
QRCode
|
The encoded symbol. |
Raises:
| Type | Description |
|---|---|
ValueError
|
If an argument is out of range or |
to_terminal
¶
Render a QR code as Unicode half blocks for a terminal.
Each text line holds two module rows, drawn with "▀", "▄",
"█", and spaces, so the symbol stays roughly square in typical
monospace fonts.
Most terminals draw light text on a dark background, so by default the
light modules (including the quiet zone) are drawn as filled blocks
and the dark modules are left as background. That scans correctly on a
dark terminal. Pass invert=True for a dark-on-light terminal, where
the filled blocks become the dark modules.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
code
|
QRCode
|
The symbol to render. |
required |
border
|
int
|
The width of the quiet zone in modules. The standard asks for 4, but 2 scans reliably and saves terminal space. |
2
|
invert
|
bool
|
Draw dark modules as filled blocks instead of light ones. |
False
|
Returns:
| Type | Description |
|---|---|
str
|
The rendering, one string with a line per pair of module rows and |
str
|
no trailing newline. |
Raises:
| Type | Description |
|---|---|
ValueError
|
If |
to_svg
¶
to_svg(
code: QRCode,
*,
border: int = 4,
scale: int = 1,
dark: str = "#000",
light: str = "#fff"
) -> str
Render a QR code as a compact SVG document.
The light background is a single <rect> and every dark module is
part of a single <path>. The viewBox is measured in modules and
shape-rendering="crispEdges" keeps the module edges sharp at any
size.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
code
|
QRCode
|
The symbol to render. |
required |
border
|
int
|
The width of the quiet zone in modules. |
4
|
scale
|
int
|
The |
1
|
dark
|
str
|
The CSS color of dark modules. |
'#000'
|
light
|
str
|
The CSS color of light modules and the quiet zone. |
'#fff'
|
Returns:
| Type | Description |
|---|---|
str
|
The SVG markup. |
Raises:
| Type | Description |
|---|---|
ValueError
|
If |
WebSocket¶
A small RFC 6455 (WebSocket) implementation on the standard library.
The dev server and the on-device dev client both need WebSockets, and neither can assume a third-party package: the client runs inside the embedded interpreter on iOS and Android, where every dependency has to be bundled. This module provides just enough of the protocol for a trusted development network:
encode_frameandFrameDecoderhandle the wire format (masking, 7/16/64-bit lengths, fragmentation, control frames).server_handshakeandclient_handshake_requestbuild the HTTP upgrade.WebSocketClientis a blocking client meant to live on a background thread (the dev client uses one; the main thread never waits on the network).
Extensions (compression) and subprotocols are not negotiated.
Classes:
| Name | Description |
|---|---|
WebSocketError |
A framing or protocol violation on the connection. |
HandshakeError |
The HTTP upgrade did not complete. |
FrameDecoder |
Incremental frame parser that reassembles fragmented messages. |
WebSocketClient |
A blocking WebSocket client for background threads. |
Functions:
| Name | Description |
|---|---|
accept_key |
Return the |
parse_http_headers |
Split an HTTP request or response head into |
server_handshake |
Build the |
client_handshake_request |
Build a client upgrade request; returns |
encode_frame |
Encode one frame. |
encode_close |
Encode a close frame carrying |
MAX_MESSAGE_BYTES
module-attribute
¶
Upper bound on one reassembled message; anything larger is a protocol error.
HandshakeError
¶
Bases: WebSocketError
The HTTP upgrade did not complete.
FrameDecoder
¶
Incremental frame parser that reassembles fragmented messages.
Feed raw bytes with feed;
it yields complete (opcode, payload) messages. Control frames
(ping, pong, close) are yielded as they arrive, even in the middle
of a fragmented data message, as the RFC allows.
Methods:
| Name | Description |
|---|---|
feed |
Consume |
WebSocketClient
¶
A blocking WebSocket client for background threads.
recv blocks until a text message arrives and transparently
answers pings; send_text is safe to call from any thread.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
url
|
str
|
|
required |
timeout
|
Optional[float]
|
Connect and read timeout in seconds. Reads that time
out raise |
30.0
|
Methods:
| Name | Description |
|---|---|
connect |
Open the TCP connection and complete the upgrade handshake. |
send_text |
Send one text message (masked, as clients must). |
recv |
Block until a text message arrives. |
close |
Send a close frame (best effort) and shut the socket. |
Attributes:
| Name | Type | Description |
|---|---|---|
connected |
bool
|
Whether the socket is open. |
recv
¶
Block until a text message arrives.
Returns None once the peer closes. Raises socket.timeout
when the read timeout elapses with no data, so callers can poll
a shutdown flag.
accept_key
¶
Return the Sec-WebSocket-Accept value for client_key.
parse_http_headers
¶
Split an HTTP request or response head into (start_line, headers).
Header names are lower-cased. raw should be the bytes up to
(and optionally including) the blank line that ends the head.
server_handshake
¶
Build the 101 Switching Protocols response for an upgrade request.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
headers
|
Dict[str, str]
|
Lower-cased request headers (see
|
required |
Raises:
| Type | Description |
|---|---|
HandshakeError
|
When the request is not a WebSocket upgrade. |
client_handshake_request
¶
Build a client upgrade request; returns (request_bytes, key).
encode_frame
¶
Encode one frame.
Clients must send masked frames and servers unmasked ones; the caller picks. Text payloads must already be UTF-8 encoded.
pythonnative.devclient¶
The on-device dev client: pair with pn start, sync sources, and serve DevTools.
Every debug build is a dev client: PythonNative Go (the prebuilt,
project-independent client) and a project's own pn run build alike.
It connects to the dev server over WebSocket and speaks version 2 of the
dev session protocol (RFC 0006):
- Handshake.
hellodescribes the client's runtime (framework and Python versions, bridge protocol, contract fingerprint, plugins, bundled distributions) and the sources its overlay already holds. The server answers with asyncof whatever is missing, or withincompatibleand the command that fixes the mismatch. - Sources and packages.
syncandupdatewrite files into the writable overlay that shadows the bundle (seepythonnative.hot_reload.configure_dev_environment) and apply them with Fast Refresh.packagesinstalls pure-Python distributions from the developer's environment into the overlay'ssite-packages. - DevTools.
rpcrequests go to the devtools agent and are answered withrpc_result; the agent's events go back asevent.tunnelmessages carry TCP streams, which the debugger uses. - Logs.
printoutput and errors stream back to the terminal and DevTools asloganderrormessages.
Clients are pointed at a server by a connect link,
pn-<application id>://connect?url=<server URL>, which pn run, pn
go, and a scanned QR code open. The last server is remembered, so a
relaunch reconnects. Without one, PythonNative Go shows its
home screen.
All network I/O runs on daemon threads; file writes happen there too, and reloads run on the application thread.
Classes:
| Name | Description |
|---|---|
DevClient |
Keep this process in sync with a dev server and serve DevTools. |
Functions:
| Name | Description |
|---|---|
dev_scheme |
The connect-link URL scheme of the dev client for |
connect_link |
|
parse_connect_link |
The server URL inside a connect link, or |
client_info |
What kind of dev client this build is: |
normalize_server_url |
Turn whatever the developer typed into a dev-client WebSocket URL. |
display_server_url |
The short form of a dev-client URL ( |
recent_servers |
Servers this client connected to, most recent first: |
current |
The running dev client, if any. |
start |
Start (or replace) the process-wide dev client for |
stop |
Stop the process-wide dev client; the dev menu keeps working locally. |
handle_link |
Connect to the server a connect link names; |
start_if_configured |
Start the dev client when a connect link or a remembered server names one. |
go_home |
Disconnect PythonNative Go and return to its home screen. |
Attributes:
| Name | Type | Description |
|---|---|---|
SESSION_PROTOCOL |
Version of the dev session protocol spoken with |
|
LINK_ENV |
Launch environment variable carrying a connect link. |
|
GoHome |
Any
|
Root component of PythonNative Go until a project arrives from a dev server. |
SESSION_PROTOCOL
module-attribute
¶
Version of the dev session protocol spoken with pn start.
LINK_ENV
module-attribute
¶
Launch environment variable carrying a connect link.
pn run ios and pn go ios launch with it: iOS asks the user to
confirm a link opened with simctl openurl, while a launch environment
variable reaches the app silently.
CLIENT_INFO_FILE
module-attribute
¶
Written into the bundled pythonnative package of debug builds: {"kind", "app_id", "scheme"}.
GoHome
module-attribute
¶
GoHome: Any = _LazyGoHome()
Root component of PythonNative Go until a project arrives from a dev server.
DevClient
¶
DevClient(
url: str,
overlay: str,
*,
entry_module: str = "app.main",
forward_logs: bool = True,
log: Optional[Logger] = None,
kind: Optional[str] = None,
agent: bool = True
)
Keep this process in sync with a dev server and serve DevTools.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
url
|
str
|
Server URL (any form accepted by
|
required |
overlay
|
str
|
Writable directory that shadows the bundled sources. |
required |
entry_module
|
str
|
The app's entry module, for the |
'app.main'
|
forward_logs
|
bool
|
Mirror |
True
|
log
|
Optional[Logger]
|
Local logger for the client's own status lines. |
None
|
kind
|
Optional[str]
|
|
None
|
agent
|
bool
|
Install the devtools agent (off in unit tests that only exercise syncing). |
True
|
Methods:
| Name | Description |
|---|---|
add_listener |
Subscribe to |
start |
Connect on a daemon thread (idempotent). |
stop |
Disconnect and stop the thread. |
send |
Send a message to the server from any thread (queued while offline). |
log |
Forward one log line to the server. |
report_error |
Forward an error report (a traceback) to the server. |
Attributes:
| Name | Type | Description |
|---|---|---|
server_label |
str
|
|
state |
str
|
|
agent |
Any
|
The devtools agent this client serves, once started. |
server_label
property
¶
server_label: str
host:port of the server, for logs (the full URL carries the dev token).
state
property
¶
state: str
"idle", "connecting", "connected", "syncing", "incompatible", or "disconnected".
add_listener
¶
Subscribe to (state, detail) changes (called on the client thread).
send
¶
Send a message to the server from any thread (queued while offline).
dev_scheme
¶
The connect-link URL scheme of the dev client for app_id.
pn- plus the application id, lowercased, with underscores turned
into hyphens (URL schemes allow letters, digits, +, -, and
.): com.example.my_app becomes pn-com.example.my-app.
connect_link
¶
<scheme>://connect?url=<server URL>, the link a QR code carries.
parse_connect_link
¶
The server URL inside a connect link, or None when link isn't one.
client_info
¶
What kind of dev client this build is: {"kind": "go" | "app", "app_id", "scheme"}.
The builder writes it into debug builds; a process without one (tests,
the host) is an "app" client.
normalize_server_url
¶
Turn whatever the developer typed into a dev-client WebSocket URL.
Accepts 192.168.1.20, 192.168.1.20:8765, http://host:port,
ws://host:port, full URLs with a path such as the device URL
pn start prints (http://192.168.1.20:8765/?token=...), and
connect links. The result always has the path /ws and the query
role=client&token=...; the dev token is kept when the input
carries one.
display_server_url
¶
The short form of a dev-client URL (host:port), without the token.
recent_servers
¶
Servers this client connected to, most recent first: [{"url", "project", "last"}].
seed_overlay
¶
Copy the bundled top package into overlay; returns the number of files.
start
¶
Start (or replace) the process-wide dev client for url.
install_local_agent
¶
Install a devtools agent that isn't connected to a server (dev menu, inspector, monitor).
handle_link
¶
Connect to the server a connect link names; True when link was one.
Installed as the deep-link interceptor by
pythonnative.hot_reload.configure_dev_environment. A link that
arrives before the dev client starts (a cold launch from a QR code)
is kept for start_if_configured.
start_if_configured
¶
Start the dev client when a connect link or a remembered server names one.
Called by bootstrap.start(dev=True). A connect link that launched
the app (as a deep link, or in PN_CONNECT_LINK) wins; otherwise the
most recent server reconnects. Returns the
client, or None when nothing is configured (PythonNative Go then
shows its home screen).
go_home
¶
Disconnect PythonNative Go and return to its home screen.
Stops the client, replaces the overlay's app with the bundled home screen, stops reconnecting at launch, and remounts.
pythonnative.preview¶
Development server and browser preview.
The browser is a protocol-4 renderer with Yoga WebAssembly layout. Python runs on the standard application asyncio loop; the transport's coordinator and network threads forward messages to their owners. The dev server watches app sources, synchronizes mobile clients, and applies compatible Fast Refresh.
PN_PLATFORM=web must be set before importing pythonnative; the CLI launches a child process with that environment when needed.
Classes:
| Name | Description |
|---|---|
PreviewSession |
Everything one |
Functions:
| Name | Description |
|---|---|
serve |
Run the dev server (and browser preview) until interrupted. |
PreviewSession
¶
PreviewSession(
project_root: str,
entry_module: str,
*,
host: str = "0.0.0.0",
port: int = 8765,
project_name: str = "",
log: Optional[Logger] = None,
config: Any = None,
debug_port: Optional[int] = None
)
Everything one pn start invocation owns; see serve.
Methods:
| Name | Description |
|---|---|
start |
Install the transport, start the server, and begin watching. |
open_devtools |
Open DevTools in the developer's browser (from any thread). |
run |
Run the main loop until |
stop_soon |
Ask |
stop |
Tear everything down. |
urls |
Every URL the server can be reached at (local first), each carrying the dev token. |
banner_lines
¶
banner_lines(
session: PreviewSession, title: str
) -> List[str]
The pn start banner: a QR code for the connect link, then the URLs.
serve
¶
serve(
entry_module: str,
*,
project_root: Optional[str] = None,
host: str = "0.0.0.0",
port: int = 8765,
project_name: str = "",
open_browser: bool = False,
log: Optional[Logger] = None,
banner: bool = True,
ready: Optional[
Callable[[PreviewSession], None]
] = None,
config: Any = None,
debug_port: Optional[int] = None
) -> None
Run the dev server (and browser preview) until interrupted.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
entry_module
|
str
|
The app's entry module ( |
required |
project_root
|
Optional[str]
|
Directory containing |
None
|
host
|
str
|
Bind address ( |
'0.0.0.0'
|
port
|
int
|
TCP port ( |
8765
|
project_name
|
str
|
Shown in the preview page. |
''
|
open_browser
|
bool
|
Open the preview page in the default browser. |
False
|
log
|
Optional[Logger]
|
Where status lines go (stderr by default). |
None
|
banner
|
bool
|
Print the connection banner. |
True
|
ready
|
Optional[Callable[[PreviewSession], None]]
|
Called once the server is listening (the CLI starts its key handler here; tests use it to drive the session). |
None
|
config
|
Any
|
The project's |
None
|
debug_port
|
Optional[int]
|
Port of the debugger proxy, or |
None
|
Raises:
| Type | Description |
|---|---|
RuntimeError
|
If |
OSError
|
If the port is taken. |
pythonnative.bridge.web¶
WebSocket transport for the browser renderer.
Network I/O and request settlement execute independently of the application asyncio loop. Python owns the component tree; the page owns DOM widgets and Yoga layout. Revisioned commits receive explicit acknowledgements, and callbacks are queued onto the Python application thread. No asyncio loop is pumped by the browser transport.
Classes:
| Name | Description |
|---|---|
WebTransport |
Bridge transport whose native side is a browser page. |
Attributes:
| Name | Type | Description |
|---|---|---|
BROWSER_MODULES |
Native modules the preview page implements; the rest use Python fallbacks. |
BROWSER_MODULES
module-attribute
¶
BROWSER_MODULES = frozenset(
{
"DevSupport",
"Host",
"Layout",
"Alert",
"Clipboard",
"Linking",
"Share",
"Haptics",
"NetInfo",
"AppState",
"Device",
"Keyboard",
"AccessibilityInfo",
"Localization",
"WebViews",
}
)
Native modules the preview page implements; the rest use Python fallbacks.
REQUEST_TIMEOUT_S
module-attribute
¶
How long a synchronous request waits for the page before giving up.
WebTransport
¶
Bridge transport whose native side is a browser page.
Install it with pythonnative.bridge.set_transport and hand it to
DevServer.set_preview_channel; the page does the rest.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
log
|
Optional[Callable[[str], None]]
|
Where diagnostics go (defaults to stderr). |
None
|
Methods:
| Name | Description |
|---|---|
protocol_version |
The page speaks whatever this package speaks; they ship together. |
set_callback |
Install the native -> Python entry point ( |
apply |
Wait for the page to acknowledge this exact commit revision. |
command |
Run an imperative command on one view; returns its JSON result or |
animate |
Handle an animation request ( |
call |
Call a native module: |
post_to_application |
Queue |
run_main_loop |
Drain fallback work until |
drain_main |
Run fallback-queue work inline for tests and return how many jobs ran. |
stop |
Make |
on_preview_connected |
A page connected; it becomes the native side. |
on_preview_disconnected |
The page went away: fail waiting requests and tear down its screens. |
on_preview_message |
Route one frame from the page. |
send_dev |
Send a |
Attributes:
| Name | Type | Description |
|---|---|---|
on_dev_message |
Optional[Callable[[Dict[str, Any]], None]]
|
Hook for |
on_peer_changed |
Optional[Callable[[bool], None]]
|
Called on the application thread with |
connected |
bool
|
Whether a page is attached. |
on_dev_message
instance-attribute
¶
Hook for ["dev", {...}] messages on the Python application thread.
on_peer_changed
instance-attribute
¶
Called on the application thread with True on connect and False on disconnect.
protocol_version
¶
protocol_version() -> int
The page speaks whatever this package speaks; they ship together.
set_callback
¶
Install the native -> Python entry point (bridge.native_callback).
apply
¶
Wait for the page to acknowledge this exact commit revision.
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: Host.post locally, page modules over the wire, others in Python.
post_to_application
¶
post_to_application(fn: Callable[[], None]) -> None
Queue fn on the application loop, or the fallback queue if it isn't running.
run_main_loop
¶
Drain fallback work until stop is called or until holds.
The preview uses this as a coordinator while application callbacks run on the standard asyncio loop. When that loop isn't running, queued jobs execute on the thread calling this method.
drain_main
¶
Run fallback-queue work inline for tests and return how many jobs ran.
on_preview_connected
¶
A page connected; it becomes the native side.
on_preview_disconnected
¶
on_preview_disconnected(peer: Any) -> None
The page went away: fail waiting requests and tear down its screens.
on_preview_message
¶
Route one frame from the page.
pythonnative.project.go¶
PythonNative Go: the prebuilt, project-independent dev client.
Go is an ordinary debug build of a fixed project: its app/main.py is
the home screen, its identity is
com.pythonnative.go, and it declares every permission the built-in
modules use, so a project's camera, location, and notification code works
in it. Any project that adds no native code runs in it: the dev server
syncs the project's sources and pure-Python packages.
Artifacts for each release are attached to the GitHub release by
scripts/build-go.py and cached per version under the shared cache
(~/Library/Caches/pythonnative/go/<version>/):
pythonnative-go-<version>-ios-simulator-arm64.zipand...-ios-simulator-x86_64.zip(the.app, one per Mac architecture: the iOS runtime is staged for a single architecture)pythonnative-go-<version>-android.apkpythonnative-go-<version>.sha256(sha256sumformat)
ensure_artifact finds the
artifact for the running pythonnative version in the cache, downloads
and verifies it, or builds it locally (a development checkout, offline,
or --build).
Classes:
| Name | Description |
|---|---|
GoError |
PythonNative Go couldn't be found, downloaded, or built. |
Functions:
| Name | Description |
|---|---|
artifact_names |
Release asset names for |
cache_dir |
Where Go artifacts for |
go_config |
Write Go's project into |
build |
Build Go for |
package |
Turn a built artifact into its release asset in |
download |
Download and verify the release artifact into the cache. |
ensure_artifact |
Return an installable Go artifact for the running framework version. |
BASE_URL_ENV
module-attribute
¶
Overrides where release assets are downloaded from (a mirror, or a test server).
GoError
¶
Bases: RuntimeError
PythonNative Go couldn't be found, downloaded, or built.
framework_version
¶
framework_version() -> str
The running pythonnative version (Go must match it exactly).
simulator_arch
¶
simulator_arch() -> str
The iOS Simulator architecture of this Mac (arm64 or x86_64).
artifact_names
¶
Release asset names for version: {"ios", "android", "sums"}.
ios names the simulator app for arch (default: this Mac's).
cache_dir
¶
Where Go artifacts for version (default: the running one) are cached.
installed_artifact
¶
The cached, ready-to-install artifact (an .app directory or an .apk), if any.
go_config
¶
Write Go's project into work_dir and load its config.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
work_dir
|
Path
|
An empty or reusable directory; |
required |
development_team
|
Optional[str]
|
An Apple team id, for a build signed for physical iPhones. |
None
|
build
¶
build(
platform: str,
*,
out_dir: Path,
log: Optional[Logger] = None,
device: bool = False,
development_team: Optional[str] = None
) -> Path
Build Go for platform and copy the artifact into out_dir.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
platform
|
str
|
|
required |
out_dir
|
Path
|
Where the |
required |
log
|
Optional[Logger]
|
Progress logger. |
None
|
device
|
bool
|
Build for physical iPhones (needs |
False
|
development_team
|
Optional[str]
|
The Apple team that signs a device build. |
None
|
Returns:
| Type | Description |
|---|---|
Path
|
The artifact in |
package
¶
Turn a built artifact into its release asset in out_dir.
A simulator app is named for this Mac's architecture, which is the one
build produced.
write_checksums
¶
Write pythonnative-go-<version>.sha256 for paths (sha256sum format).
download
¶
ensure_artifact
¶
ensure_artifact(
platform: str,
*,
force_build: bool = False,
fetch: Fetch = _fetch,
log: Optional[Logger] = None
) -> Path
Return an installable Go artifact for the running framework version.
Looks in the cache, then downloads the release asset, then builds it locally (which needs Xcode or the Android toolchain, once).
pythonnative.project.fingerprint¶
Native build fingerprints: know when pn run can skip the toolchain.
A debug build only has to be rebuilt when one of its native inputs
changes: pythonnative.toml, the bundled native template, the
pythonnative package itself, project-local native plugins, or the
build flavor (platform, SDK, release). Edits under app/ don't count;
the dev server syncs those into the running app and Fast Refresh applies
them, exactly as Metro does for a React Native debug build.
compute hashes those
inputs into one hex digest. pn run writes it next to the build
after a successful toolchain run (see
write_stamp) and, when
the digest is unchanged and a dev server is running to deliver the
latest sources, reinstalls the previous artifact instead of staging and
compiling again.
The hash covers file contents, not mtimes, so touching a file or re-cloning the repository doesn't invalidate a build.
Functions:
| Name | Description |
|---|---|
hash_tree |
Hash every file under |
compute |
Digest every native input of a build. |
read_stamp |
Return the stamp written by the last successful build, or |
write_stamp |
Record |
Attributes:
| Name | Type | Description |
|---|---|---|
STAMP_NAME |
File written into |
STAMP_NAME
module-attribute
¶
File written into build/<platform>/ after a successful native build.
hash_tree
¶
Hash every file under root (relative path + contents), deterministically.
Build outputs, caches, and bytecode are skipped so a checkout that has been built hashes the same as a fresh one.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
root
|
Path
|
Directory (or single file) to hash. A missing path hashes as the empty tree. |
required |
into
|
Optional['hashlib._Hash']
|
An existing hasher to feed instead of creating one. |
None
|
Returns:
| Type | Description |
|---|---|
str
|
The hex digest (of |
compute
¶
compute(
config: AppConfig,
platform: str,
*,
template_root: Path,
lib_root: Path,
release: bool = False,
ios_sdks: Sequence[str] = (),
extra: Optional[Dict[str, str]] = None
) -> str
Digest every native input of a build.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
config
|
AppConfig
|
The loaded project configuration (its file is hashed). |
required |
platform
|
str
|
|
required |
template_root
|
Path
|
The bundled native template directory. |
required |
lib_root
|
Path
|
The |
required |
release
|
bool
|
Release builds are distinct from debug builds. |
False
|
ios_sdks
|
Sequence[str]
|
The iOS SDK slices being staged. |
()
|
extra
|
Optional[Dict[str, str]]
|
Additional key/value inputs (host arch, tool versions). |
None
|
Returns:
| Type | Description |
|---|---|
str
|
A hex SHA-256 digest. |
read_stamp
¶
Return the stamp written by the last successful build, or None.
Next steps¶
- Fast Refresh and the hot reload API for the device-side reload.
- Browser preview.