FazBrowse GitHub Viewer | Trending |
URL:
| Home
Tools: [Download Repo ZIP]   [Original HTTPS Page]

feat!: add typed navigation, themes, stores, and hook linting by owenthcarey · Pull Request #109 · pythonnative/pythonnative · GitHub

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension .cpp  (1) .ini  (1) .js  (2) .json  (1) .kt  (4) .md  (63) .py  (250) .sh  (1) .swift  (5) .yaml  (12) .yml  (2) All 11 file types selected
Only manifest files
Deleted files Viewed files
Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Unified
Split
Hide whitespace
Diff view
Unified
Split
Hide whitespace
  •  
  •  
  •  
15 changes: 15 additions & 0 deletions .github/workflows/ci.yml
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,21 @@ jobs:
- name: Type check (MyPy)
run: uv run mypy

# The Inbox and e2e apps each own a top-level ``app`` package, so
# they're checked from their own directories rather than whole-tree.
- name: Type check the Inbox example (strict)
working-directory: examples/inbox
env:
MYPYPATH: ../inbox-extension/src
run: uv run --project ../.. mypy --strict app

- name: Type check the e2e suite (strict)
working-directory: examples/e2e-suite
run: uv run --project ../.. mypy --strict app

- name: Rules of hooks (pn lint)
run: uv run pn lint src/pythonnative examples

# Produces the sdist and wheel through the setuptools backend. Note
# this builds but does not install them, so the previous non-editable
# install smoke test is not replaced.
Expand Down
13 changes: 9 additions & 4 deletions CONTRIBUTING.md
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,9 @@ Unsolicited pull requests for issues that are already assigned or already have a
- `journal.py`: the undo journal a failed render pass rolls back through
- `native_views/`: the thin backend accessor (`get_backend` / `set_backend`) and the bridge backend that serializes commits
- `native_modules/`: device module facades and their Python fallbacks
- `navigation/`: navigators, container, hooks, linking, theme, and the navigation ref
- `navigation/`: navigator values, screens, container, hooks, deep-link table, and the navigation ref
- `theme.py`, `style.py`, `store.py`: the app `Theme`, `StyleSheet` namespaces and style resolution, and `Store`
- `lint.py`: the `pn lint` rules-of-hooks checker
- `testing/`: the headless testing library and the shipped pytest plugin (`testing/pytest_plugin.py`)
- `bridge/`: JSON codec and per-platform transports into the native rendering core
- `cli/`: `pn` command
Expand Down Expand Up @@ -201,7 +203,7 @@ Recommended scopes (choose the smallest, most accurate unit; prefer module/direc
- `bridge`: wire codec, transports, handshake, and native-to-Python callback (`bridge/`)
- `cli`: CLI tool and `pn` command (`src/pythonnative/cli/`)
- `component`: the `@component` decorator, `Component`, and `memo` (`component.py`)
- `components`: declarative element-creating functions (`components/`)
- `components`: declarative element-creating functions and their shared props (`components/`)
- `element`: Element descriptor class (`element.py`)
- `events`: tag-based event routing between native views and Python callbacks (`events.py`)
- `gestures`: gesture descriptors and the pure-Python recognition arbiter (`gestures.py`)
Expand All @@ -213,10 +215,11 @@ Recommended scopes (choose the smallest, most accurate unit; prefer module/direc
- `hot_reload`: module reloader and Fast Refresh (`hot_reload.py`)
- `journal`: render undo journal (`journal.py`)
- `layout`: Yoga layout integration and the host binding (`layout.py`, `native/yoga/`)
- `lint`: the `pn lint` rules-of-hooks checker (`lint.py`)
- `mutations`: batched mutation ops between reconciler and native backends (`mutations.py`)
- `native_modules`: native module registry, Python facades, and Python fallbacks (`native_modules/`)
- `native_views`: view backend accessor and bridge backend (`native_views/`)
- `navigation`: navigation state, container, navigators, hooks, linking, theme, and ref (`navigation/`)
- `navigation`: navigation state, container, navigators, screens, hooks, linking, and ref (`navigation/`)
- `net`: awaitable HTTP client (`net.py`)
- `package`: `src/pythonnative/__init__.py` exports and package boundary
- `platform`: `Platform.OS`/`Platform.select` and version detection (`platform.py`)
Expand All @@ -227,9 +230,11 @@ Recommended scopes (choose the smallest, most accurate unit; prefer module/direc
- `runtime`: framework-wide asyncio loop and thread-safe future helpers (`runtime.py`)
- `sdk`: public extension SDK for custom native components (`sdk/`)
- `storage`: AsyncStorage key/value persistence and `use_persisted_state` (`storage.py`)
- `store`: the `Store` app-state container and `use_store` (`store.py`)
- `scheduler`: render batching and transition queues (`scheduler.py`)
- `style`: StyleSheet and theming (`style.py`)
- `style`: the `Style` type, style resolution, and `StyleSheet` (`style.py`)
- `testing`: public test utilities and the pytest plugin (`testing/`)
- `theme`: `Theme`, `ThemeProvider`, `use_theme`, and `use_styles` (`theme.py`)
- `utils`: shared utilities (`utils.py`)

- Other scopes:
Expand Down
7 changes: 4 additions & 3 deletions README.md
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,8 @@ PythonNative is a cross-platform toolkit for building native Android and iOS app
- **Native widgets and layout:** Swift and Kotlin component managers own UIKit and Android widgets. The shared Yoga C++ engine computes layout beside the controls, using platform measurements for text and intrinsic sizes.
- **Standard asyncio:** Components, effects, event handlers, and tasks run on a dedicated Python application thread. Native UI threads handle widgets, scrolling, and animation frames. Component-owned tasks are canceled on unmount.
- **Incremental reconciliation:** State changes update affected component subtrees. Versioned bridge commits validate operations and acknowledge revisions; events and controlled text inputs carry identities that prevent stale updates.
- **Native lists and navigation:** UIKit collection views and Android recycler views recycle cells for fixed or variable row sizes, grids, sections, and horizontal lists. Native navigation containers present logical screen roots at every nesting level while providers and component state remain in the shared Python tree; navigators take screen options, groups, themes, tab-bar styles, and a navigation ref usable outside components.
- **Typed, Pythonic authoring:** Components return `pn.Node` and their signatures are their props, so a strict type checker verifies element trees and conditional children. Screens are components whose parameters are their route params (`nav.push(ItemScreen(id=42))`), navigators are module-level values with deep links derived from each screen's `path`, one `Theme` dataclass styles the app and its navigators, `StyleSheet` namespaces give styles typed names, and `pn.Store` holds app state with selector-based re-renders. `pn lint` checks the rules of hooks.
- **Native lists and navigation:** UIKit collection views and Android recycler views recycle cells for fixed or variable row sizes, grids, sections, and horizontal lists. Native navigation containers present logical screen roots at every nesting level while providers and component state remain in the shared Python tree; navigators take screen options, groups, tab-bar styles, and a navigation ref usable outside components.
- **Animation and gestures:** Serialized animation graphs support timing with named or bezier easing, springs, React Native's decay model, arithmetic, interpolation, and native scroll and gesture bindings. Swift and Kotlin recognize mobile gestures with Gesture Handler-style activation offsets and update supported animation bindings without Python work on every frame.
- **Assets and graphics:** Drop images, fonts, and SVGs under `app/assets/` and reference them by path. Density variants, bundled fonts, a vector `Icon` set, `Svg` drawing, gradients, and blur render natively on both platforms and in the browser preview.
- **Device APIs:** Python facades expose camera, location, notifications, storage, permissions, keyboard, dimensions and pixel ratio, device info, localization, accessibility settings, and other native services, with a Python fallback for headless tests. Test permissions and platform behavior on your deployment targets.
Expand All @@ -62,15 +63,15 @@ import pythonnative as pn


@pn.component
def App():
def App() -> pn.Node:
count, set_count = pn.use_state(0)
return pn.Column(
pn.Text(f"Count: {count}", style=pn.style(font_size=24, bold=True)),
pn.Button(
"Tap me",
on_press=lambda: set_count(count + 1),
),
style=pn.style(spacing=12, padding=16),
style=pn.style(gap=12, padding=16),
)
```

Expand Down
10 changes: 5 additions & 5 deletions docs/api/appearance.md
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,12 @@ import pythonnative as pn


@pn.component
def SchemeAwareBadge():
def SchemeAwareBadge() -> pn.Node:
scheme = pn.use_color_scheme() # "light" or "dark"
theme = pn.use_theme() # built-in theme for the scheme
theme = pn.use_theme() # the theme for the scheme
return pn.Text(
f"Currently {scheme}",
style={"color": theme.text_color},
style={"color": theme.colors.text},
)
```

Expand All @@ -38,5 +38,5 @@ pn.appearance.set_color_scheme(None) # follow the system again
## Next steps

- Theming with [`use_theme`][pythonnative.use_theme] and
[`ThemeContext`][pythonnative.ThemeContext]: see
[Style](style.md) and the [Styling guide](../guides/styling.md).
[`ThemeProvider`][pythonnative.ThemeProvider]: see
[Theme](theme.md) and the [Styling guide](../guides/styling.md#themes).
18 changes: 13 additions & 5 deletions docs/api/cli.md
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters
Original file line number Diff line number Diff line change
Expand Up @@ -27,10 +27,11 @@ the documented behavior never drifts from the code.
satisfied. See [PyPI packages](../guides/pypi-packages.md).
- `pn start [entry]`: run the dev server. It watches `app/`, 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` (default `0.0.0.0`), `--open` to also open
the browser preview. See the
[Development workflow](../guides/dev-workflow.md).
their logs, and serves the browser preview page. Every client needs
your dev token from `~/.pythonnative/dev-token`, which the printed URLs
carry. Flags: `--port` (default 8765), `--host` (default `0.0.0.0`),
`--open` to also open the browser preview. See the
[Development workflow](../guides/dev-workflow.md#the-dev-token).
- `pn preview [entry]`: `pn start` plus opening the browser preview in
your default browser. Flags: `--port`, `--host`, `--no-open`. See the
[Browser preview guide](../guides/browser-preview.md).
Expand All @@ -45,7 +46,7 @@ the documented behavior never drifts from the code.
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` (where `pn start` listens),
passed to the app; include the `?token=` from `pn start`), `--port` (where `pn start` listens),
`--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
Expand All @@ -62,6 +63,13 @@ the documented behavior never drifts from the code.
scripting; the config
error goes to stderr instead, and a missing config still exits 1.
- `pn clean`: remove the local `build/` directory.
- `pn lint [paths...]`: check the rules of hooks without running the
code. With no paths it lints `app/`, or the current directory if
there's no `app/`. Each finding prints as `path:line:col: CODE
message`. Flag: `--json` to print a JSON array of findings to stdout
for scripting; the summary line goes to stderr instead. Exits 1 when
there are findings and 2 when a path doesn't exist. See the
[Linting guide](../guides/linting.md).
- `pn --version` (`-V`): print the installed PythonNative version.

::: pythonnative.cli.pn
Expand Down
22 changes: 11 additions & 11 deletions docs/api/component-properties.md
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters
Original file line number Diff line number Diff line change
Expand Up @@ -39,9 +39,8 @@ All components accept these layout properties in their `style` dict:
- `margin`: outer spacing. Accepts a number (all sides), or a dict with any
of `horizontal`, `vertical`, `left`, `top`, `right`, `bottom`.
- `padding`: inner spacing on container elements. Same shape as `margin`.
- `spacing`: gap between children of a flex container (applied along the
main axis).
- `gap`: alias for `spacing`.
- `gap`: space between children of a flex container. `row_gap` and
`column_gap` set one axis.

### Position

Expand Down Expand Up @@ -92,7 +91,7 @@ pn.View(*children, style={
"justify_content": "center",
"align_items": "center",
"overflow": "hidden",
"spacing": 8,
"gap": 8,
"padding": 16,
"background_color": "#F5F5F5",
})
Expand All @@ -106,7 +105,7 @@ Flex container properties (inside `style`):
- `justify_content`: `"flex_start"`, `"center"`, `"flex_end"`, `"space_between"`, `"space_around"`, `"space_evenly"`
- `align_items`: `"stretch"`, `"flex_start"`, `"center"`, `"flex_end"`
- `overflow`: `"visible"` (default), `"hidden"`
- `spacing`, `padding`, `background_color`
- `gap`, `padding`, `background_color`

Containers also fully support absolute positioning for their children:

Expand Down Expand Up @@ -165,8 +164,8 @@ pn.Button(title, on_press=handler, style={"color": "#FFF", "background_color": "
## Column / Row

```python
pn.Column(*children, style={"spacing": 12, "padding": 16, "align_items": "center"})
pn.Row(*children, style={"spacing": 8, "justify_content": "space_between"})
pn.Column(*children, style={"gap": 12, "padding": 16, "align_items": "center"})
pn.Row(*children, style={"gap": 8, "justify_content": "space_between"})
```

Convenience wrappers for `View` with fixed `flex_direction`:
Expand All @@ -176,7 +175,7 @@ Convenience wrappers for `View` with fixed `flex_direction`:

- `*children`: child elements (positional)
- Style properties:
- `spacing`: gap between children (dp / pt)
- `gap`: space between children (dp / pt)
- `padding`: inner padding (int for all sides, or dict with `horizontal`, `vertical`, `left`, `top`, `right`, `bottom`)
- `align_items`: cross-axis alignment: `"stretch"`, `"flex_start"`, `"center"`, `"flex_end"`, `"leading"`, `"trailing"`
- `justify_content`: main-axis distribution: `"flex_start"`, `"center"`, `"flex_end"`, `"space_between"`, `"space_around"`, `"space_evenly"`
Expand Down Expand Up @@ -303,7 +302,7 @@ pn.Image(source="https://example.com/photo.jpg",
on_load_start=lambda: ..., on_load=lambda e: ..., on_load_end=lambda: ...,
on_error=lambda message: ...,
fade_duration=200, headers={"Authorization": f"Bearer {token}"},
style={"width": 200, "height": 150, "scale_type": "cover"})
scale_type="cover", style={"width": 200, "height": 150})
```

- `source`: a bundled [`Asset`][pythonnative.Asset], an image URL
Expand Down Expand Up @@ -586,7 +585,8 @@ pn.Element("TabBar", {
})
```

Native tab bar, typically created automatically by `Tab.Navigator`.
Native tab bar, typically created automatically by a
[`TabNavigator`][pythonnative.TabNavigator].

| Platform | Native view |
|----------|--------------------------|
Expand Down Expand Up @@ -642,7 +642,7 @@ pn.FlatList(data=items, render_item=render_fn, key_extractor=key_fn,
```python
pn.SectionList(sections=[pn.Section(key="a", title="A", data=["Apple"])],
render_item=lambda item, i, s: pn.Text(item),
render_section_header=lambda section, s: pn.Text(section["title"]),
render_section_header=lambda section, s: pn.Text(section.title),
sticky_section_headers=True, inverted=False,
item_separator=lambda: pn.View(style={"height": 1, "background_color": "#EEE"}),
on_viewable_items_changed=handler)
Expand Down
22 changes: 17 additions & 5 deletions docs/api/component.md
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,15 @@ The [`@component`][pythonnative.component.component] decorator turns a plain
function into a [`Component`][pythonnative.Component]: calling it
returns an [`Element`][pythonnative.Element] instead of running the
body, and the reconciler runs the body (with hooks) when the element
mounts or its props change. Positional arguments are children; keyword
arguments are props. [`memo`][pythonnative.memo] skips re-rendering
when props are unchanged.

[](){ #pythonnative.Component.keyed }
mounts or its props change. The component's signature is its prop
list, so type checkers verify every call. Positional `*children`
receive child nodes; keyword arguments are props. Key a component's
element with [`Element.with_key`][pythonnative.element.Element.with_key]
(`Row(item).with_key(item.id)`), since `key=` isn't a prop.
[`memo`][pythonnative.memo] skips re-rendering when props are
unchanged. In development builds, props are also checked against the
function's annotations; see
[`prop_checks`](#development-prop-checks).

::: pythonnative.component
options:
Expand All @@ -17,6 +21,14 @@ when props are unchanged.
members_order: source
filters: ["!^_"]

## Development prop checks

::: pythonnative.prop_checks
options:
show_root_heading: false
show_root_toc_entry: false
members: false

## Next steps

- Learn the model in [Components](../concepts/components.md).
Expand Down
27 changes: 23 additions & 4 deletions docs/api/components.md
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,11 @@ return immutable [`Element`][pythonnative.Element] descriptors; nothing
is mounted to a native view tree until the
[`Reconciler`][pythonnative.reconciler.Reconciler] processes them.

Every argument is a keyword argument on the factory, which makes it
part of the generated native contract for iOS, Android, and the browser
preview. Platform notes (an Android-only ripple, an iOS-only keyboard
Containers take their children positionally as `*children: pn.Node`,
and every other argument is a keyword argument on the factory, which
makes it part of the generated native contract for iOS, Android, and
the browser preview. Keywords shared by many components are declared
once as [shared props](#shared-props) instead of in every factory. Platform notes (an Android-only ripple, an iOS-only keyboard
appearance) are recorded on the argument itself. Event callbacks
receive the [typed payloads](#typed-event-payloads) below rather than
dicts, and a `ref=` argument receives a typed
Expand All @@ -26,6 +28,23 @@ For the visual and layout properties accepted by each component's
- "!^_"
- "!^(ContentSizeEvent|ImageLoadEvent|KeyPressEvent|LayoutEvent|ScrollEvent|SelectionEvent|WebNavigationEvent)$"

## Shared props

Every built-in view accepts the
[`AccessibilityProps`][pythonnative.AccessibilityProps] keywords, and
`View`, `Column`, `Row`, and `Pressable` also accept the
[`ViewProps`][pythonnative.ViewProps] layout and gesture keywords.
Factories declare them as `**props: Unpack[...]`, so a type checker
validates each keyword as if it were spelled out in the signature.
Built-in props are validated at run time only in development builds.

::: pythonnative.components.props
options:
show_root_heading: false
show_root_toc_entry: false
members_order: source
filters: ["!^_"]

## List data and records

`ListData` publishes explicit keyed edits to `FlatList`. `Section` gives each
Expand Down Expand Up @@ -60,5 +79,5 @@ callbacks. Field names match the wire payload, which is what lets

- Wire interactions with [Hooks](hooks.md).
- Drive a mounted view through its [handle](handles.md).
- Compose styles with [`StyleSheet`][pythonnative.StyleSheet].
- Group named styles with [`StyleSheet`][pythonnative.StyleSheet].
- Build navigation with [`NavigationContainer`][pythonnative.NavigationContainer].
9 changes: 9 additions & 0 deletions docs/api/devserver.md
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,15 @@ guide for how they fit together; this page documents the modules.
members_order: source
filters: ["!^_"]

### Authentication

::: pythonnative.devserver.auth
options:
show_root_heading: false
show_root_toc_entry: false
members_order: source
filters: ["!^_"]

### File watcher and source snapshots

::: pythonnative.devserver.watcher
Expand Down
2 changes: 2 additions & 0 deletions docs/api/diagnostics.md
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ Most apps never call this module directly; it exists so framework
code (and custom component libraries) can report problems in a way
that reaches the developer instead of disappearing into a log.

[](){ #pythonnative.diagnostics }

::: pythonnative.diagnostics
options:
show_root_heading: false
Expand Down
7 changes: 7 additions & 0 deletions docs/api/element.md
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,13 @@ tree. `Element` instances are produced by the
You almost never construct an `Element` by hand; the factory functions
exist precisely so app code stays in plain Python.

[`Node`][pythonnative.element.Node] is the type of anything that can
appear in a tree: an element, `None` or a `bool` for "nothing", or a
(possibly nested) iterable of nodes. Built-in containers take
`*children: pn.Node`, and components return `pn.Node`.
[`Element.with_key`][pythonnative.element.Element.with_key] returns a keyed
copy, the typed way to key a user component.

::: pythonnative.element
options:
show_root_heading: false
Expand Down
Loading
Loading

Back | FazBrowse Home | New Git URL