CLI (pn)¶
Reference for the pn console script. The implementation lives in
pythonnative.cli.pn; this page renders its docstrings directly so
the documented behavior never drifts from the code.
Subcommands¶
pn init [name]: scaffold a new project (createsapp/,pythonnative.toml,.gitignore). With a name it creates./<name>/and scaffolds into it; the name must match^[a-z][a-z0-9_-]*$. Without one it uses the current directory, whatever it's called. Flag:--forceto overwrite existing files or scaffold into a non-empty directory. See Configuration.pn doctor [android|ios]: diagnose the local toolchain and validatepythonnative.toml, including that apython3.Xmatching[app].python_versionis available for package resolution. Exits non-zero when something will block a build. Flag:--jsonto print a JSON array of check results to stdout for scripting; the verdict line goes to stderr instead, and the exit status is unchanged.pn deps [android|ios]: resolve[requirements].packagesfor every device target (iOS device, iOS Simulator, and each Android ABI) without installing anything, and report the wheel each package would use, flagging binary wheels, their index, and downgrades. Flags:--jsonfor a machine-readable report,--pythonto pick the interpreter that runs pip. Exits non-zero when any target can't be satisfied. See PyPI packages.pn start [entry]: run the dev server. It watchesapp/, syncs every save to each connected debug build with Fast Refresh, relays their logs, and serves the browser preview page. Flags:--port(default 8765),--host(default0.0.0.0),--opento also open the browser preview. See the Development workflow.pn preview [entry]:pn startplus opening the browser preview in your default browser. Flags:--port,--host,--no-open. See the Browser preview guide.pn devices [android|ios]: list connected devices, emulators, and simulators with the identifiers--deviceaccepts. Flag:--jsonto print a JSON array to stdout for scripting; the "no devices" hints go to stderr instead, and an empty list prints[]and exits 0.pn run android|ios: build, install, and launch a debug build that connects to the running dev server. The native toolchain runs only when a native input changed (config, template,pythonnativeitself, native plugins); otherwise the previous artifact is reinstalled. Flags:--device(target a specific device by identifier or name),--prepare-only,--no-logs,--rebuild(force the toolchain),--dev-server URL(override the server URL baked into the app),--port(wherepn startlistens),--dev-client(build a shell app with a connect screen that loads any project from a dev server).pn logs android|ios: stream logs from the running app without rebuilding. Flag:--device(target a specific device by identifier or name, same aspn run). Physical iOS devices aren't supported for log streaming; use Console.app or Xcode > Devices and Simulators.pn build android|ios: build distributable artifacts (release by default). Flags:--debugfor the debug variant,--uploadto send an iOS release build to App Store Connect. See Building for release.pn app-id android|ios: print the resolved application id (Android) or bundle id (iOS), handy for scripts and CI. Flag:--jsonto print a{"platform": "...", "app_id": "..."}object to stdout for scripting; the config error goes to stderr instead, and a missing config still exits 1.pn clean: remove the localbuild/directory.pn --version(-V): print the installed PythonNative version.
pn CLI: scaffold, diagnose, start, run, and build PythonNative apps.
The console script pn (declared in pyproject.toml) dispatches to:
pn init [name]: scaffold a new project (pythonnative.toml+app/) into./name/, or into the current directory when no name is given.pn doctor [platform]: diagnose the local toolchain and config, as a report or as JSON with--json.pn deps [platform]: resolve[requirements].packagesfor every device target and report which wheels would be used (or why a package can't be installed), without building anything.pn start: run the dev server. It renders the app in a browser tab and syncs every save to every connected debug build (simulators, emulators, physical devices) with Fast Refresh; device logs stream back into the same terminal.pn preview:pn startplus opening the browser preview.pn devices [platform]: list connected devices, emulators, and simulators, as a table or as JSON with--json.pn run android|ios [--device D]: stage + build + install + launch a debug build that connects to the dev server. The native project is only rebuilt when something outsideapp/changed.pn logs android|ios [--device D]: stream logs from the running app without rebuilding.pn build android|ios: produce standalone artifacts (signed APK/AAB, or an iOS archive/IPA, optionally uploaded to App Store Connect).pn app-id android|ios: print the resolved application/bundle id (handy for scripts and CI), as plain text or as JSON with--json.pn clean: remove the localbuild/directory.
The heavy lifting lives in the pythonnative.project and
pythonnative.devserver packages; this module is a thin,
side-effect-y shell that wires arguments to them and handles the
device-facing steps (simulator boot, launch, log streaming) that can't
be unit tested.
Functions:
| Name | Description |
|---|---|
init_project |
Scaffold a new PythonNative project. |
doctor_command |
Run toolchain/config diagnostics and exit non-zero on errors. |
app_id_command |
Print the resolved application id (Android) or bundle id (iOS). |
deps_command |
Report how |
start_command |
Run the dev server (and the browser preview) for the current project. |
preview_command |
|
devices_command |
List connected devices, emulators, and simulators. |
run_project |
Stage, build, install, and launch a debug build that talks to |
build_project |
Build standalone, distributable artifacts for |
logs_command |
Stream logs from the running app without rebuilding. |
clean_project |
Remove the local |
codegen_command |
Generate native contracts after importing extension schema modules. |
main |
Entry point for the |
Attributes:
| Name | Type | Description |
|---|---|---|
DEFAULT_DEV_PORT |
Port |
DEFAULT_DEV_PORT
module-attribute
¶
Port pn start listens on unless --port says otherwise.
init_project
¶
init_project(args: Namespace) -> None
Scaffold a new PythonNative project.
Given a name, this creates ./<name>/ and scaffolds into it. Without
one, it scaffolds into the current directory and names the project after
it. Either way it writes app/main.py, pythonnative.toml, and
.gitignore.
A name you pass has to match ^[a-z][a-z0-9_-]*$: lowercase letters,
digits, -, and _, starting with a letter. Anything else is
refused with a legal suggestion. That keeps the directory name and the
name field in the generated config identical, in the same spirit as
flutter create and cargo new. The name taken from the current
directory when you pass none is used as-is, so an existing directory
with any name still works.
The name also has to be a single directory name, so the project always
lands inside the current directory. Anything that reads as a path, such
as a/b, .., or /tmp/app, is refused, and so is a name that
resolves somewhere else, such as a symlink to another directory.
It won't scaffold into a target directory that already holds files, and it
won't overwrite any of the three paths above; pass --force to override
both. An existing but empty target directory is fine. A plain file at
./<name> is always refused, since --force can't turn it into a
directory, and --force lifts neither of the rules above.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
args
|
Namespace
|
Parsed namespace with |
required |
doctor_command
¶
doctor_command(args: Namespace) -> None
Run toolchain/config diagnostics and exit non-zero on errors.
With --json, stdout carries a JSON array and nothing else, one
object per check (see CheckResult.to_dict), and the verdict line
goes to stderr. The exit status stays keyed to the worst check level
either way.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
args
|
Namespace
|
Parsed namespace with optional |
required |
app_id_command
¶
app_id_command(args: Namespace) -> None
Print the resolved application id (Android) or bundle id (iOS).
With --json, stdout carries one object,
{"platform": "...", "app_id": "..."}, and nothing else; the config
error goes to stderr instead so stdout stays parseable. Keys may be
added to that object later, but none will be removed or renamed, so
a caller reading app_id keeps working.
A missing or invalid config still exits 1, with or without the flag.
That differs from pn devices --json, which exits 0 on an empty
result, because "no devices" is a valid answer to a query whereas
an unreadable config means there is no id to report at all.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
args
|
Namespace
|
Parsed namespace with |
required |
deps_command
¶
deps_command(args: Namespace) -> None
Report how [requirements].packages resolve for each device target.
Runs pip in its cross-platform dry-run mode once per target (iOS
device, iOS Simulator, and one per Android ABI) and prints the
wheel each package would use, flagging binary wheels and their
source index. Exits non-zero when any target can't be satisfied,
so it doubles as a CI gate. --json emits the same data as a
machine-readable document.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
args
|
Namespace
|
Parsed namespace with optional |
required |
start_command
¶
Run the dev server (and the browser preview) for the current project.
Re-execs under PN_PLATFORM=web so every module binds to the
browser backend, then hands off to pythonnative.preview.serve.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
args
|
Namespace
|
Parsed namespace ( |
required |
open_browser
|
bool
|
Open the preview page once the server is up
( |
False
|
preview_command
¶
preview_command(args: Namespace) -> None
pn preview: pn start that also opens the browser preview.
devices_command
¶
devices_command(args: Namespace) -> None
List connected devices, emulators, and simulators.
Prints an aligned table and exits 1 when nothing is connected.
With --json, stdout carries a JSON array and nothing else, one
object per device (see Device.to_dict), so it stays parseable.
The "no devices" hints go to stderr instead, an empty result prints
[], and the exit status is 0 either way, since "no devices" is a
valid answer for a script rather than a failure.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
args
|
Namespace
|
Parsed namespace with optional |
required |
run_project
¶
run_project(args: Namespace) -> None
Stage, build, install, and launch a debug build that talks to pn start.
The native toolchain only runs when a native input changed (see
pythonnative.project.fingerprint) or when no dev server is up
to deliver the latest sources; otherwise the previous artifact is
reinstalled, which turns the edit/relaunch loop from minutes into
seconds.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
args
|
Namespace
|
Parsed namespace ( |
required |
build_project
¶
build_project(args: Namespace) -> None
Build standalone, distributable artifacts for platform.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
args
|
Namespace
|
Parsed namespace ( |
required |
logs_command
¶
logs_command(args: Namespace) -> None
Stream logs from the running app without rebuilding.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
args
|
Namespace
|
Parsed namespace ( |
required |
clean_project
¶
clean_project(args: Namespace) -> None
Remove the local build/ directory.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
args
|
Namespace
|
Parsed namespace (unused). |
required |
Next steps¶
- See the Getting started walkthrough.
- Read the Development workflow for how
pn startandpn runwork together.