Skip to content

Assets

Files under app/assets/ are bundled into the iOS app, the Android APK, and the browser preview, and are addressed from Python by relative path:

import pythonnative as pn

logo = pn.asset("images/logo.png")   # Asset("images/logo.png")
pn.Image(source=logo, style=pn.style(width=120, height=40))
config = pn.asset("data/config.json").read_text()

The Assets guide covers density variants, fonts, icons, and how the folder is bundled and synced.

Bundled assets: images, fonts, and other files shipped inside the app.

Everything under app/assets/ is copied into the native application by pn build and synced to connected devices by pn start. Code refers to those files with asset:

import pythonnative as pn

logo = pn.asset("images/logo.png")
pn.Image(source=logo, style=pn.style(width=120, height=40))

An Asset is a small frozen value. On the wire it becomes the URI asset://images/logo.png; the native runtimes resolve that against the dev overlay (when connected to pn start) and then the bundle, and pick the density variant (logo@2x.png, logo@3x.png) closest to the device scale. Python never needs to know the device's pixel ratio or where the bundle lives.

Fonts work the same way: drop .ttf or .otf files into app/assets/fonts/ and reference them by family name with font_family. The family, weight, and style are read from the font files themselves (see read_font_face), so no mapping file is needed.

The build tool and the dev server describe an asset directory with an AssetManifest (see scan), which lists every file, groups density variants by base name, and carries the parsed font faces. The native runtimes read the manifest that ships in the bundle and receive a fresh one over the bridge whenever the dev overlay changes.

Modules:

Name Description
fonts

Read the identifying tables of TrueType and OpenType font files.

Classes:

Name Description
FontFace

One face (a family at one weight and style) provided by a font file.

FontParseError

The file isn't a TrueType or OpenType font this reader understands.

Asset

A file under app/assets/, addressed by its relative path.

AssetManifest

Everything a runtime needs to know about one assets directory.

Functions:

Name Description
read_font_face

Return the FontFace described by a font file.

weight_from_name

Infer a CSS weight from a style name like "Semi Bold Italic".

normalize_path

Return path as a clean, asset-relative POSIX path.

is_asset_uri

Whether value is an asset:// URI string.

split_variant

Split "images/logo@2x.png" into ("images/logo.png", 2.0).

choose_variant

Pick the variant path best suited to a device scale.

asset

Reference a file bundled under app/assets/.

scan

Describe the assets directory at root.

write_manifest

Scan root and write pn_assets.json inside it.

assets_roots

Directories that may hold app/assets/ files, most specific first.

font_faces

The font faces bundled with the app.

generation

A counter bumped whenever synced assets change (for caches).

bump_generation

Invalidate asset caches after a sync; returns the new generation.

configure_native

Tell the native runtime about the dev overlay's assets.

Attributes:

Name Type Description
ASSETS_DIR

Directory under app/ that holds runtime assets.

ASSET_SCHEME

URI scheme the native runtimes resolve against the bundled assets.

MANIFEST_NAME

File name of the manifest the builder writes next to staged assets.

ASSETS_DIR module-attribute

ASSETS_DIR = 'assets'

Directory under app/ that holds runtime assets.

ASSET_SCHEME module-attribute

ASSET_SCHEME = 'asset://'

URI scheme the native runtimes resolve against the bundled assets.

MANIFEST_NAME module-attribute

MANIFEST_NAME = 'pn_assets.json'

File name of the manifest the builder writes next to staged assets.

FONT_SUFFIXES module-attribute

FONT_SUFFIXES = ('.ttf', '.otf', '.ttc')

File suffixes scanned for font faces.

FontFace dataclass

FontFace(
    family: str,
    weight: int,
    italic: bool,
    postscript_name: str,
    path: str,
)

One face (a family at one weight and style) provided by a font file.

Attributes:

Name Type Description
family str

The family name apps pass as font_family.

weight int

CSS-style weight from 100 to 900.

italic bool

Whether the face is italic or oblique.

postscript_name str

The PostScript name, which is what UIFont(name:) resolves after the file is registered.

path str

The file path relative to app/assets/ (forward slashes).

FontParseError

Bases: ValueError

The file isn't a TrueType or OpenType font this reader understands.

Asset dataclass

Asset(path: str)

A file under app/assets/, addressed by its relative path.

Build one with asset. Assets compare and hash by path, so they're safe as dict keys, in StyleSheet values, and as use_effect dependencies.

Attributes:

Name Type Description
path str

The normalized path relative to app/assets/, using forward slashes ("images/logo.png").

Methods:

Name Description
read_bytes

Return the file's contents.

read_text

Return the file's contents decoded as text.

exists

Whether some copy of the asset (overlay, bundle, or APK) exists.

uri property

uri: str

The asset:// URI the native runtimes resolve.

name property

name: str

The file name without directories.

suffix property

suffix: str

The file extension, including the dot (".png").

read_bytes

read_bytes() -> bytes

Return the file's contents.

Looks in the dev overlay and the bundled app/assets/ directory first; on Android, where bundled assets live inside the APK, the native Assets module reads them.

Raises:

Type Description
FileNotFoundError

If no copy of the asset can be found.

read_text

read_text(encoding: str = 'utf-8') -> str

Return the file's contents decoded as text.

exists

exists() -> bool

Whether some copy of the asset (overlay, bundle, or APK) exists.

AssetManifest dataclass

AssetManifest(
    files: Tuple[str, ...] = (),
    variants: Dict[str, Dict[str, str]] = dict(),
    fonts: Tuple[FontFace, ...] = (),
)

Everything a runtime needs to know about one assets directory.

Attributes:

Name Type Description
files Tuple[str, ...]

Every asset path, sorted.

variants Dict[str, Dict[str, str]]

base path -> {scale string -> variant path} for every image with density variants (and for base images, so a lookup is one dictionary hit).

fonts Tuple[FontFace, ...]

The font faces found under fonts/ (or anywhere in the directory with a font suffix).

Methods:

Name Description
resolve

Return the variant of path that best matches scale.

faces

Every face of family (case-insensitive).

to_dict

A JSON-ready form (what the builder writes and the bridge sends).

from_dict

Rebuild a manifest written by to_dict.

dumps

The manifest as pretty JSON.

resolve

resolve(path: str, scale: float = 1.0) -> Optional[str]

Return the variant of path that best matches scale.

Parameters:

Name Type Description Default
path str

An asset path or asset:// URI (a base name; a variant name is normalized to its base first).

required
scale float

The device pixel ratio.

1.0

Returns:

Type Description
Optional[str]

The path to load, or None if the manifest has no such file.

faces

faces(family: str) -> List[FontFace]

Every face of family (case-insensitive).

to_dict

to_dict() -> Dict[str, Any]

A JSON-ready form (what the builder writes and the bridge sends).

from_dict classmethod

from_dict(data: Mapping[str, Any]) -> 'AssetManifest'

Rebuild a manifest written by to_dict.

dumps

dumps() -> str

The manifest as pretty JSON.

read_font_face

read_font_face(
    source: Union[str, Path, bytes], *, path: str = ""
) -> FontFace

Return the FontFace described by a font file.

Parameters:

Name Type Description Default
source Union[str, Path, bytes]

A file path or the file's bytes.

required
path str

The asset-relative path recorded on the result. Defaults to the file name when source is a path.

''

Raises:

Type Description
FontParseError

If the data isn't a supported font.

weight_from_name

weight_from_name(subfamily: str) -> Optional[int]

Infer a CSS weight from a style name like "Semi Bold Italic".

Longer words are checked first so "extrabold" doesn't match "bold". Returns None when nothing matches.

normalize_path

normalize_path(path: str) -> str

Return path as a clean, asset-relative POSIX path.

Accepts "images/logo.png", "./images/logo.png", an asset:// URI, or Windows separators; rejects absolute paths, .. segments, and empty input.

Raises:

Type Description
ValueError

If the path can't name a file under app/assets/.

is_asset_uri

is_asset_uri(value: Any) -> bool

Whether value is an asset:// URI string.

split_variant

split_variant(path: str) -> Tuple[str, float]

Split "images/logo@2x.png" into ("images/logo.png", 2.0).

Paths without a density suffix return scale 1.0 and themselves.

choose_variant

choose_variant(
    variants: Mapping[float, str], scale: float
) -> Optional[str]

Pick the variant path best suited to a device scale.

Preference order: an exact match, the nearest variant above scale (so images are downsampled rather than upsampled), then the largest available. Returns None when variants is empty.

asset

asset(path: str) -> Asset

Reference a file bundled under app/assets/.

Parameters:

Name Type Description Default
path str

Path relative to app/assets/, such as "images/logo.png". Density variants (logo@2x.png) are picked automatically; always name the base file.

required

Returns:

Type Description
Asset

An Asset.

Raises:

Type Description
ValueError

If path is absolute, empty, or escapes the assets directory.

Example
pn.Image(source=pn.asset("images/logo.png"))
pn.Text("Hi", style=pn.style(font_family="Poppins"))  # app/assets/fonts/Poppins-*.ttf

scan

scan(
    root: Union[str, Path], *, log: Any = None
) -> AssetManifest

Describe the assets directory at root.

Hidden files, editor swap files, and __pycache__ are skipped. Font files that can't be parsed are reported through log (when given) and omitted, so one broken font never breaks a build.

Parameters:

Name Type Description Default
root Union[str, Path]

The app/assets/ directory. A missing directory yields an empty manifest.

required
log Any

Optional callable that receives warning strings.

None

write_manifest

write_manifest(
    root: Union[str, Path], *, log: Any = None
) -> AssetManifest

Scan root and write pn_assets.json inside it.

The build tool calls this on the staged copy of app/assets/ so the native runtimes can resolve variants and register fonts without listing the bundle at startup. Returns the manifest that was written. A missing root is created so the bundle always carries a manifest.

assets_roots

assets_roots() -> List[Path]

Directories that may hold app/assets/ files, most specific first.

The dev overlay (when pn start is connected) comes before the bundled or checked-out app/ package. On Android the bundled copy isn't a directory at all (it lives in the APK), so only the overlay is listed there; Asset.read_bytes falls back to the native Assets module.

font_faces

font_faces() -> Tuple[FontFace, ...]

The font faces bundled with the app.

Readable roots (the dev overlay, a checkout, the iOS bundle) are scanned directly. On Android the bundled app/assets/ lives inside the APK rather than on disk, so its faces come from the manifest the build wrote, read through the native Assets module.

generation

generation() -> int

A counter bumped whenever synced assets change (for caches).

bump_generation

bump_generation() -> int

Invalidate asset caches after a sync; returns the new generation.

configure_native

configure_native(
    paths: Optional[Iterable[str]] = None,
) -> bool

Tell the native runtime about the dev overlay's assets.

Called by the dev client after a sync and by bootstrap.start in debug builds. It scans <overlay>/app/assets/ and sends the resulting manifest to the native Assets module, which then resolves asset:// URIs against the overlay before the bundle and registers any overlay fonts.

Parameters:

Name Type Description Default
paths Optional[Iterable[str]]

The synced paths ("app/assets/...") when known. When given and none of them is under app/assets/, nothing is sent.

None

Returns:

Type Description
bool

True if a manifest was pushed.

manifest_for_sync

manifest_for_sync(files: Sequence[str]) -> List[str]

Filter a synced path list to the asset paths (helper for the dev client).

Fonts

Bundled .ttf and .otf files are parsed at build time so font_family can refer to them by family name.

Read the identifying tables of TrueType and OpenType font files.

The build tool and the dev server need to know, for every file under app/assets/fonts/, which family it belongs to and which weight and style it provides, so font_family="Poppins", font_weight=700 can be matched to Poppins-Bold.ttf on every platform without a hand-written mapping. That information lives inside the font: the name table carries the family and PostScript names and the OS/2 table carries the weight class and the italic flag.

This module reads exactly those tables (plus head as a fallback for the style bits) with :mod:struct; it never parses glyph data. It handles .ttf, .otf (CFF outlines), and the first face of a .ttc collection.

Classes:

Name Description
FontParseError

The file isn't a TrueType or OpenType font this reader understands.

FontFace

One face (a family at one weight and style) provided by a font file.

Functions:

Name Description
weight_from_name

Infer a CSS weight from a style name like "Semi Bold Italic".

read_font_face

Return the FontFace described by a font file.

FontParseError

Bases: ValueError

The file isn't a TrueType or OpenType font this reader understands.

FontFace dataclass

FontFace(
    family: str,
    weight: int,
    italic: bool,
    postscript_name: str,
    path: str,
)

One face (a family at one weight and style) provided by a font file.

Attributes:

Name Type Description
family str

The family name apps pass as font_family.

weight int

CSS-style weight from 100 to 900.

italic bool

Whether the face is italic or oblique.

postscript_name str

The PostScript name, which is what UIFont(name:) resolves after the file is registered.

path str

The file path relative to app/assets/ (forward slashes).

weight_from_name

weight_from_name(subfamily: str) -> Optional[int]

Infer a CSS weight from a style name like "Semi Bold Italic".

Longer words are checked first so "extrabold" doesn't match "bold". Returns None when nothing matches.

read_font_face

read_font_face(
    source: Union[str, Path, bytes], *, path: str = ""
) -> FontFace

Return the FontFace described by a font file.

Parameters:

Name Type Description Default
source Union[str, Path, bytes]

A file path or the file's bytes.

required
path str

The asset-relative path recorded on the result. Defaults to the file name when source is a path.

''

Raises:

Type Description
FontParseError

If the data isn't a supported font.

Next steps