diff --git a/.commitlintrc.yml b/.commitlintrc.yml deleted file mode 100644 index 455e892..0000000 --- a/.commitlintrc.yml +++ /dev/null @@ -1,30 +0,0 @@ -extends: - - '@commitlint/config-conventional' - -rules: - type-enum: - - 2 - - always - - - build - - chore - - ci - - docs - - feat - - fix - - perf - - refactor - - revert - - style - - test - scope-case: - - 0 - subject-case: - - 0 - header-max-length: - - 1 - - always - - 100 - body-max-line-length: - - 0 - footer-max-line-length: - - 0 diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml deleted file mode 100644 index a177889..0000000 --- a/.github/workflows/ci.yml +++ /dev/null @@ -1,53 +0,0 @@ -name: CI - -on: - push: - branches: [ '**' ] - pull_request: - branches: [ '**' ] - -jobs: - build: - runs-on: ubuntu-latest - strategy: - matrix: - python-version: ['3.10', '3.11', '3.12'] - - steps: - - name: Checkout - uses: actions/checkout@v4 - - - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v5 - with: - python-version: ${{ matrix.python-version }} - - - name: Install tools - run: | - python -m pip install --upgrade pip - pip install -e ".[ci]" - - - name: Lint (Ruff) - run: | - ruff check . - - - name: Format check (Black) - run: | - black --check src examples tests - - - name: Type check (MyPy) - run: | - mypy --install-types --non-interactive - - - name: Install package - run: | - pip install . - - - name: Build package - run: | - pip install build - python -m build - - - name: Run tests - run: | - pytest -q diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml deleted file mode 100644 index ddce20b..0000000 --- a/.github/workflows/docs.yml +++ /dev/null @@ -1,30 +0,0 @@ -name: Docs - -on: - push: - branches: [ main ] - -permissions: - contents: write - -jobs: - build: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - uses: actions/setup-python@v5 - with: - python-version: '3.11' - - name: Install docs deps - run: | - python -m pip install --upgrade pip - pip install -e ".[docs]" - - name: Build site - run: | - python -m mkdocs build --strict - - name: Deploy to GitHub Pages - uses: peaceiris/actions-gh-pages@v3 - with: - github_token: ${{ secrets.GITHUB_TOKEN }} - publish_dir: ./site - cname: docs.pythonnative.com diff --git a/.github/workflows/e2e.yml b/.github/workflows/e2e.yml deleted file mode 100644 index 7935a34..0000000 --- a/.github/workflows/e2e.yml +++ /dev/null @@ -1,79 +0,0 @@ -name: E2E - -on: - push: - branches: [main] - pull_request: - branches: [main] - workflow_dispatch: - -jobs: - e2e-android: - runs-on: ubuntu-latest - timeout-minutes: 30 - - steps: - - name: Checkout - uses: actions/checkout@v4 - - - name: Set up Python - uses: actions/setup-python@v5 - with: - python-version: '3.11' - - - name: Set up Java 17 - uses: actions/setup-java@v4 - with: - distribution: 'temurin' - java-version: '17' - - - name: Install PythonNative - run: pip install -e . - - - name: Install Maestro - run: | - curl -Ls "https://get.maestro.mobile.dev" | bash - echo "$HOME/.maestro/bin" >> $GITHUB_PATH - - - name: Build, install, and run E2E tests - uses: reactivecircus/android-emulator-runner@v2 - with: - api-level: 31 - arch: x86_64 - script: >- - cd examples/hello-world && - pn run android && - sleep 5 && - cd ../.. && - maestro test tests/e2e/android.yaml - - e2e-ios: - runs-on: macos-latest - timeout-minutes: 30 - - steps: - - name: Checkout - uses: actions/checkout@v4 - - - name: Set up Python - uses: actions/setup-python@v5 - with: - python-version: '3.11' - - - name: Install PythonNative - run: pip install -e . - - - name: Install Maestro and idb - run: | - curl -Ls "https://get.maestro.mobile.dev" | bash - echo "$HOME/.maestro/bin" >> $GITHUB_PATH - brew tap facebook/fb && brew install idb-companion - - - name: Build and launch iOS app - working-directory: examples/hello-world - run: pn run ios - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - - - name: Run E2E tests - run: maestro --platform ios test tests/e2e/ios.yaml diff --git a/.github/workflows/pr-lint.yml b/.github/workflows/pr-lint.yml deleted file mode 100644 index f616d1a..0000000 --- a/.github/workflows/pr-lint.yml +++ /dev/null @@ -1,46 +0,0 @@ -name: PR Lint - -on: - pull_request: - types: [opened, edited, synchronize, reopened] - -permissions: - pull-requests: read - -jobs: - pr-title: - name: PR title (Conventional Commits) - runs-on: ubuntu-latest - steps: - - name: Validate PR title - uses: amannn/action-semantic-pull-request@v5 - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - with: - types: | - build - chore - ci - docs - feat - fix - perf - refactor - revert - style - test - requireScope: false - subjectPattern: ^[a-z].+[^.]$ - subjectPatternError: | - Subject "{subject}" must start with a lowercase letter and must not - end with a period. - Example: "feat(cli): add init subcommand" - - commits: - name: Commit messages - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - with: - fetch-depth: 0 - - uses: wagoid/commitlint-github-action@v6 diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml deleted file mode 100644 index 20ac529..0000000 --- a/.github/workflows/release.yml +++ /dev/null @@ -1,47 +0,0 @@ -name: Release - -on: - push: - branches: [main] - workflow_dispatch: - -jobs: - release: - name: Semantic Release - runs-on: ubuntu-latest - concurrency: - group: release - cancel-in-progress: false - permissions: - contents: write - id-token: write - - steps: - - name: Checkout - uses: actions/checkout@v4 - with: - fetch-depth: 0 - - - name: Set up Python - uses: actions/setup-python@v5 - with: - python-version: '3.12' - - - name: Install build tools - run: python -m pip install -U pip build - - - name: Python Semantic Release - id: release - uses: python-semantic-release/python-semantic-release@v9 - with: - github_token: ${{ secrets.GITHUB_TOKEN }} - - - name: Build package - if: steps.release.outputs.released == 'true' - run: python -m build - - - name: Publish to PyPI - if: steps.release.outputs.released == 'true' - uses: pypa/gh-action-pypi-publish@release/v1 - with: - skip-existing: true diff --git a/.gitignore b/.gitignore deleted file mode 100644 index 4f1c160..0000000 --- a/.gitignore +++ /dev/null @@ -1,167 +0,0 @@ -# Byte-compiled / optimized / DLL files -__pycache__/ -*.py[cod] -*$py.class - -# C extensions -*.so - -# Distribution / packaging -.Python -build/ -develop-eggs/ -dist/ -downloads/ -eggs/ -.eggs/ -lib/ -lib64/ -parts/ -sdist/ -var/ -wheels/ -share/python-wheels/ -*.egg-info/ -.installed.cfg -*.egg -MANIFEST - -# PyInstaller -# Usually these files are written by a python script from a template -# before PyInstaller builds the exe, so as to inject date/other infos into it. -*.manifest -*.spec - -# Installer logs -pip-log.txt -pip-delete-this-directory.txt - -# Unit test / coverage reports -htmlcov/ -.tox/ -.nox/ -.coverage -.coverage.* -.cache -nosetests.xml -coverage.xml -*.cover -*.py,cover -.hypothesis/ -.pytest_cache/ -cover/ - -# Translations -*.mo -*.pot - -# Django stuff: -*.log -local_settings.py -db.sqlite3 -db.sqlite3-journal -**/staticfiles/ - -# Flask stuff: -instance/ -.webassets-cache - -# Scrapy stuff: -.scrapy - -# Sphinx documentation -docs/_build/ - -# PyBuilder -.pybuilder/ -target/ - -# Jupyter Notebook -.ipynb_checkpoints - -# IPython -profile_default/ -ipython_config.py - -# pyenv -# For a library or package, you might want to ignore these files since the code is -# intended to run in multiple environments; otherwise, check them in: -# .python-version - -# pipenv -# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. -# However, in case of collaboration, if having platform-specific dependencies or dependencies -# having no cross-platform support, pipenv may install dependencies that don't work, or not -# install all needed dependencies. -#Pipfile.lock - -# poetry -# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. -# This is especially recommended for binary packages to ensure reproducibility, and is more -# commonly ignored for libraries. -# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control -#poetry.lock - -# pdm -# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control. -#pdm.lock -# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it -# in version control. -# https://pdm.fming.dev/#use-with-ide -.pdm.toml - -# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm -__pypackages__/ - -# Celery stuff -celerybeat-schedule -celerybeat.pid - -# SageMath parsed files -*.sage.py - -# Environments -.env -.venv -env/ -venv/ -ENV/ -env.bak/ -venv.bak/ - -# Spyder project settings -.spyderproject -.spyproject - -# Rope project settings -.ropeproject - -# mkdocs documentation -/site - -# mypy -.mypy_cache/ -.dmypy.json -dmypy.json - -# Pyre type checker -.pyre/ - -# pytype static type analyzer -.pytype/ - -# Cython debug symbols -cython_debug/ - -# PyCharm -# JetBrains specific template is maintained in a separate JetBrains.gitignore that can -# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore -# and can be added to the global gitignore or merged into this file. For a more nuclear -# option (not recommended) you can uncomment the following to ignore the entire idea folder. -.idea/ - -# Metadata -*_metadata.json - -# macOS -.DS_Store diff --git a/examples/hello-world/.gitignore b/.nojekyll similarity index 100% rename from examples/hello-world/.gitignore rename to .nojekyll diff --git a/404.html b/404.html new file mode 100644 index 0000000..5801082 --- /dev/null +++ b/404.html @@ -0,0 +1,967 @@ + + + +
+ + + + + + + + + + + + + + + + + + + +
-
-
- Build native Android and iOS apps in Python. -
- - - -- Documentation · - Getting Started · - Examples · - Contributing -
- ---- - -## Overview - -PythonNative is a cross-platform toolkit for building native Android and iOS apps in Python. It provides a **declarative, React-like component model** with hooks and automatic reconciliation, powered by Chaquopy on Android and rubicon-objc on iOS. Write function components with `use_state`, `use_effect`, and friends, just like React, and let PythonNative handle creating and updating native views. - -## Features - -- **Declarative UI:** Describe *what* your UI should look like with element functions (`Text`, `Button`, `Column`, `Row`, etc.). PythonNative creates and updates native views automatically. -- **Hooks and function components:** Manage state with `use_state`, side effects with `use_effect`, and navigation with `use_navigation`, all through one consistent pattern. -- **`style` prop:** Pass all visual and layout properties through a single `style` dict, composable via `StyleSheet`. -- **Virtual view tree + reconciler:** Element trees are diffed and patched with minimal native mutations, similar to React's reconciliation. -- **Direct native bindings:** Python calls platform APIs directly through Chaquopy and rubicon-objc, with no JavaScript bridge. -- **CLI scaffolding:** `pn init` creates a ready-to-run project; `pn run android` and `pn run ios` build and launch your app. -- **Navigation:** Push and pop screens with argument passing via the `use_navigation()` hook. -- **Bundled templates:** Android Gradle and iOS Xcode templates are included, so scaffolding requires no network access. - -## Quick Start - -### Installation - -```bash -pip install pythonnative -``` - -### Usage - -```python -import pythonnative as pn - - -@pn.component -def MainPage(): - count, set_count = pn.use_state(0) - return pn.Column( - pn.Text(f"Count: {count}", style={"font_size": 24}), - pn.Button( - "Tap me", - on_click=lambda: set_count(count + 1), - ), - style={"spacing": 12, "padding": 16}, - ) -``` - -## Documentation - -Visit [docs.pythonnative.com](https://docs.pythonnative.com/) for the full documentation, including getting started guides, platform-specific instructions for Android and iOS, API reference, and working examples. - -## Contributing - -Contributions are welcome. Please see [CONTRIBUTING.md](CONTRIBUTING.md) for setup instructions, coding standards, and guidelines for submitting pull requests. - -## License - -[MIT](LICENSE) diff --git a/api/component-properties/index.html b/api/component-properties/index.html new file mode 100644 index 0000000..a560f0c --- /dev/null +++ b/api/component-properties/index.html @@ -0,0 +1,1669 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +All visual and layout properties are passed via the style dict (or list of dicts) to element functions. Behavioural properties (callbacks, data, content) remain as keyword arguments.
style)¶All components accept these layout properties in their style dict:
width — fixed width in dp (Android) / pt (iOS)height — fixed heightflex — flex grow factor (shorthand for flex_grow)flex_grow — how much a child grows to fill available spaceflex_shrink — how much a child shrinks when space is limitedmargin — outer spacing (int, float, or dict with horizontal, vertical, left, top, right, bottom)min_width, max_width — width constraintsmin_height, max_height — height constraintsalign_self — override parent alignment ("stretch", "flex_start", "center", "flex_end")key — stable identity for reconciliation (passed as a kwarg, not inside style)pn.View(*children, style={
+ "flex_direction": "column",
+ "justify_content": "center",
+ "align_items": "center",
+ "overflow": "hidden",
+ "spacing": 8,
+ "padding": 16,
+ "background_color": "#F5F5F5",
+})
+Universal flex container (like React Native's View). Defaults to flex_direction: "column".
Flex container properties (inside style):
flex_direction — "column" (default), "row", "column_reverse", "row_reverse"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_colorpn.Text(text, style={"font_size": 18, "color": "#333", "bold": True, "text_align": "center"})
+text — display string (positional)font_size, color, bold, text_align, background_color, max_linespn.Button(title, on_click=handler, style={"color": "#FFF", "background_color": "#007AFF", "font_size": 16})
+title — button label (positional)on_click — callback () -> Noneenabled — interactive state (kwarg, default True)color, background_color, font_sizepn.Column(*children, style={"spacing": 12, "padding": 16, "align_items": "center"})
+pn.Row(*children, style={"spacing": 8, "justify_content": "space_between"})
+Convenience wrappers for View with fixed flex_direction:
Column = View with flex_direction: "column" (always vertical)Row = View with flex_direction: "row" (always horizontal)
*children — child elements (positional)
spacing — gap 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"overflow — "visible" (default), "hidden"background_color — container backgroundpn.SafeAreaView(*children, style={"background_color": "#FFF", "padding": 8})
+Container that respects safe area insets (notch, status bar).
+pn.ScrollView(child, style={"background_color": "#FFF"})
+pn.TextInput(value="", placeholder="Enter text", on_change=handler, secure=False,
+ style={"font_size": 16, "color": "#000", "background_color": "#FFF"})
+on_change — callback (str) -> None receiving new textpn.Image(source="https://example.com/photo.jpg", style={"width": 200, "height": 150, "scale_type": "cover"})
+source — image URL (http://... / https://...) or local resource namewidth, height, scale_type ("cover", "contain", "stretch", "center"), background_colorpn.Switch(value=False, on_change=handler)
+on_change — callback (bool) -> Nonepn.Slider(value=0.5, min_value=0.0, max_value=1.0, on_change=handler)
+on_change — callback (float) -> Nonepn.ProgressBar(value=0.5, style={"background_color": "#EEE"})
+value — 0.0 to 1.0pn.ActivityIndicator(animating=True)
+pn.WebView(url="https://example.com")
+pn.Spacer(size=16, flex=1)
+size — fixed dimension in dp / ptflex — flex grow factorpn.Pressable(child, on_press=handler, on_long_press=handler)
+Wraps any child element with tap/long-press handling.
+pn.Modal(*children, visible=show_modal, on_dismiss=handler, title="Confirm",
+ style={"background_color": "#FFF"})
+Overlay dialog shown when visible=True.
pn.Element("TabBar", {
+ "items": [
+ {"name": "Home", "title": "Home"},
+ {"name": "Settings", "title": "Settings"},
+ ],
+ "active_tab": "Home",
+ "on_tab_select": handler,
+})
+Native tab bar — typically created automatically by Tab.Navigator.
| Platform | +Native view | +
|---|---|
| Android | +BottomNavigationView |
+
| iOS | +UITabBar |
+
items — list of {"name": str, "title": str} dicts defining each tabactive_tab — the name of the currently active tabon_tab_select — callback (str) -> None receiving the selected tab namepn.FlatList(data=items, render_item=render_fn, key_extractor=key_fn,
+ separator_height=1, style={"background_color": "#FFF"})
+data — list of itemsrender_item — (item, index) -> Element functionkey_extractor — (item, index) -> str for stable keysseparator_height — spacing between itemspythonnative.create_page(...) — called internally by native templates to bootstrap the root component. You don't call this directly.
pythonnative.Text, Button, Column, Row, ScrollView, TextInput, Image, Switch, ProgressBar, ActivityIndicator, WebView, Spacerpythonnative.View, SafeAreaView, Modal, Slider, Pressable, FlatListEach returns an Element descriptor. Visual and layout properties are passed via style={...}. See the Component Property Reference for full details.
pythonnative.ErrorBoundary(child, fallback=...) — catches render errors in child and displays fallback instead. fallback may be an Element or a callable that receives the exception and returns an Element.
pythonnative.Element — the descriptor type returned by element functions. You generally don't create these directly.
Function component primitives:
+pythonnative.component — decorator to create a function componentpythonnative.use_state(initial) — local component statepythonnative.use_reducer(reducer, initial_state) — reducer-based state management; returns (state, dispatch)pythonnative.use_effect(effect, deps) — side effects, run after native commitpythonnative.use_navigation() — navigation handle (navigate/go_back/get_params)pythonnative.use_route() — convenience hook for current route paramspythonnative.use_focus_effect(effect, deps) — like use_effect but only runs when the screen is focusedpythonnative.use_memo(factory, deps) — memoised valuespythonnative.use_callback(fn, deps) — stable function referencespythonnative.use_ref(initial) — mutable ref objectpythonnative.use_context(context) — read from contextpythonnative.create_context(default) — create a new contextpythonnative.Provider(context, value, child) — provide a context valueDeclarative, component-based navigation system:
+pythonnative.NavigationContainer(child) — root container for the navigation treepythonnative.create_stack_navigator() — create a stack-based navigator (returns object with .Navigator and .Screen)pythonnative.create_tab_navigator() — create a tab-based navigatorpythonnative.create_drawer_navigator() — create a drawer-based navigatorpythonnative.batch_updates() — context manager that batches multiple state updates into a single re-renderpythonnative.StyleSheet — utility for creating and composing style dictspythonnative.ThemeContext — built-in theme context (defaults to light theme)pythonnative.native_modules.Camera — photo capture and gallery pickingpythonnative.native_modules.Location — GPS / location servicespythonnative.native_modules.FileSystem — app-scoped file I/Opythonnative.native_modules.Notifications — local push notificationspythonnative.utils.IS_ANDROID — platform flag with robust detection for Chaquopy/Android.pythonnative.utils.get_android_context() — returns the current Android Activity/Context when running on Android.pythonnative.utils.set_android_context(ctx) — set internally during page bootstrapping; you generally don't call this directly.pythonnative.utils.get_android_fragment_container() — returns the current Fragment container ViewGroup used for page rendering.pythonnative.reconciler.Reconciler — diffs element trees and applies minimal native mutations. Supports key-based child reconciliation, function components, context providers, and error boundaries. Effects are flushed after each mount/reconcile pass. Used internally by create_page.
pythonnative.hot_reload.FileWatcher — watches a directory for file changes and triggers a callback. Used by pn run --hot-reload.
pythonnative.hot_reload.ModuleReloader — reloads changed Python modules on the device and triggers page re-rendering.
pythonnative.native_views.NativeViewRegistry — maps element type names to platform-specific handlers. Use set_registry() to inject a mock for testing.
The native_views package is organised into submodules:
pythonnative.native_views.base — shared ViewHandler protocol and utilities (parse_color_int, resolve_padding, LAYOUT_KEYS)pythonnative.native_views.android — Android handlers (only imported at runtime on Android via Chaquopy)pythonnative.native_views.ios — iOS handlers (only imported at runtime on iOS via rubicon-objc)0&&i[i.length-1])&&(p[0]===6||p[0]===2)){r=0;continue}if(p[0]===3&&(!i||p[1]>i[0]&&p[1]=e.length&&(e=void 0),{value:e&&e[o++],done:!e}}};throw new TypeError(t?"Object is not iterable.":"Symbol.iterator is not defined.")}function K(e,t){var r=typeof Symbol=="function"&&e[Symbol.iterator];if(!r)return e;var o=r.call(e),n,i=[],s;try{for(;(t===void 0||t-- >0)&&!(n=o.next()).done;)i.push(n.value)}catch(a){s={error:a}}finally{try{n&&!n.done&&(r=o.return)&&r.call(o)}finally{if(s)throw s.error}}return i}function B(e,t,r){if(r||arguments.length===2)for(var o=0,n=t.length,i;o