pythonnative 0.22.0__py3-none-any.whl → 0.22.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- pythonnative/__init__.py +1 -1
- pythonnative/animated.py +6 -6
- pythonnative/cli/pn.py +1 -1
- pythonnative/components.py +12 -12
- pythonnative/events.py +5 -5
- pythonnative/gestures.py +3 -3
- pythonnative/hooks.py +3 -3
- pythonnative/hot_reload.py +4 -4
- pythonnative/layout.py +3 -3
- pythonnative/mutations.py +1 -1
- pythonnative/native_modules/camera.py +1 -1
- pythonnative/native_modules/haptics.py +2 -2
- pythonnative/native_modules/location.py +1 -1
- pythonnative/native_modules/permissions.py +2 -2
- pythonnative/native_modules/secure_store.py +1 -1
- pythonnative/native_views/android.py +20 -20
- pythonnative/native_views/base.py +3 -3
- pythonnative/native_views/desktop.py +7 -7
- pythonnative/native_views/ios.py +23 -23
- pythonnative/navigation.py +4 -4
- pythonnative/net.py +3 -3
- pythonnative/platform.py +1 -1
- pythonnative/platform_metrics.py +5 -5
- pythonnative/preview.py +3 -3
- pythonnative/project/builder.py +1 -1
- pythonnative/project/config.py +3 -3
- pythonnative/project/doctor.py +2 -2
- pythonnative/project/icons.py +1 -1
- pythonnative/project/ios.py +1 -1
- pythonnative/project/permissions.py +2 -2
- pythonnative/project/runtime_assets.py +3 -3
- pythonnative/reconciler.py +9 -9
- pythonnative/runtime.py +8 -8
- pythonnative/screen.py +5 -5
- pythonnative/sdk/_components.py +1 -1
- pythonnative/storage.py +3 -3
- pythonnative/style.py +1 -1
- pythonnative/templates/ios_template/ios_template.xcodeproj/project.pbxproj +2 -2
- pythonnative/templates/ios_template/ios_templateUITests/ios_templateUITests.swift +1 -1
- {pythonnative-0.22.0.dist-info → pythonnative-0.22.1.dist-info}/METADATA +13 -13
- {pythonnative-0.22.0.dist-info → pythonnative-0.22.1.dist-info}/RECORD +45 -45
- {pythonnative-0.22.0.dist-info → pythonnative-0.22.1.dist-info}/WHEEL +0 -0
- {pythonnative-0.22.0.dist-info → pythonnative-0.22.1.dist-info}/entry_points.txt +0 -0
- {pythonnative-0.22.0.dist-info → pythonnative-0.22.1.dist-info}/licenses/LICENSE +0 -0
- {pythonnative-0.22.0.dist-info → pythonnative-0.22.1.dist-info}/top_level.txt +0 -0
pythonnative/native_views/ios.py
CHANGED
|
@@ -8,7 +8,7 @@ frame application. Handlers are registered with the
|
|
|
8
8
|
|
|
9
9
|
**Batched protocol**: the registry applies the reconciler's mutation
|
|
10
10
|
ops; handlers receive callable-free props. User callbacks never reach
|
|
11
|
-
this module
|
|
11
|
+
this module; every interaction (taps, text edits, scrolls, gestures)
|
|
12
12
|
is forwarded through
|
|
13
13
|
[`dispatch_event`][pythonnative.events.dispatch_event] keyed by the
|
|
14
14
|
view's reconciler-assigned tag.
|
|
@@ -171,7 +171,7 @@ def _has_event(view: Any, name: str) -> bool:
|
|
|
171
171
|
# ======================================================================
|
|
172
172
|
#
|
|
173
173
|
# rubicon-objc's ``@objc_method`` FFI bridge is unreliable on iOS arm64
|
|
174
|
-
# for some delegate callback shapes
|
|
174
|
+
# for some delegate callback shapes, in particular when UIKit passes
|
|
175
175
|
# tagged pointers (e.g. NSIndexPath) or invokes selectors that return
|
|
176
176
|
# objects, the FFI closure ends up in CPython's ``_ctypes.O_get`` and
|
|
177
177
|
# crashes on bogus PyObject* dereferences.
|
|
@@ -688,7 +688,7 @@ def _register_control_action(control: Any, events_mask: int, handler: Any) -> No
|
|
|
688
688
|
|
|
689
689
|
The UIControl counterpart of ``_register_action``: control events
|
|
690
690
|
(TouchUpInside, ValueChanged, ...) must not be delivered through
|
|
691
|
-
rubicon's ``@objc_method`` bridge either
|
|
691
|
+
rubicon's ``@objc_method`` bridge either; the trampoline's ``sender``
|
|
692
692
|
marshaling is what crashed UISwitch toggles on iOS 18.x (the action
|
|
693
693
|
fired, but touching the marshaled ``sender`` segfaulted). The raw IMP
|
|
694
694
|
receives only the sender *pointer*; ``handler`` closures read any
|
|
@@ -763,7 +763,7 @@ def _make_gesture_handler(
|
|
|
763
763
|
pass
|
|
764
764
|
elif kind == "swipe":
|
|
765
765
|
# Discrete: UIKit only calls us on recognition, and only the
|
|
766
|
-
# recognizer whose direction matched fires
|
|
766
|
+
# recognizer whose direction matched fires, so the bound
|
|
767
767
|
# per-recognizer direction is the actual swipe direction.
|
|
768
768
|
payload["state"] = "ended"
|
|
769
769
|
payload["direction"] = direction
|
|
@@ -1154,7 +1154,7 @@ class IOSViewHandler(ViewHandler):
|
|
|
1154
1154
|
|
|
1155
1155
|
``decay`` (and any unknown kind) returns ``False`` so the Python
|
|
1156
1156
|
ticker integrates the exact physics. Off-main-thread starts also
|
|
1157
|
-
fall back
|
|
1157
|
+
fall back; UIKit animation APIs are main-thread-only.
|
|
1158
1158
|
"""
|
|
1159
1159
|
if native_view is None or not isinstance(spec, dict):
|
|
1160
1160
|
return False
|
|
@@ -1190,7 +1190,7 @@ class IOSViewHandler(ViewHandler):
|
|
|
1190
1190
|
|
|
1191
1191
|
|
|
1192
1192
|
class FlexContainerHandler(IOSViewHandler):
|
|
1193
|
-
"""Container for flex layout
|
|
1193
|
+
"""Container for flex layout, a bare `UIView`.
|
|
1194
1194
|
|
|
1195
1195
|
All flex semantics (direction, alignment, distribution, padding)
|
|
1196
1196
|
are computed by the layout engine and applied via
|
|
@@ -1416,7 +1416,7 @@ class ButtonHandler(IOSViewHandler):
|
|
|
1416
1416
|
|
|
1417
1417
|
# ``scrollViewDidScroll:`` hands the delegate a ``UIScrollView*``. rubicon's
|
|
1418
1418
|
# ``@objc_method`` FFI bridge is unreliable for delegate callbacks that take
|
|
1419
|
-
# ObjC object arguments on arm64 (see the module header note)
|
|
1419
|
+
# ObjC object arguments on arm64 (see the module header note); on the arm64
|
|
1420
1420
|
# simulator the callback simply never reaches Python, so ``on_scroll`` would
|
|
1421
1421
|
# silently never fire. Exactly like the UITabBar delegate, we therefore build
|
|
1422
1422
|
# the delegate class with raw libobjc and dispatch through a CFUNCTYPE IMP.
|
|
@@ -1462,7 +1462,7 @@ if _PN_SCROLL_DELEGATE_CLS:
|
|
|
1462
1462
|
|
|
1463
1463
|
|
|
1464
1464
|
class ScrollViewHandler(IOSViewHandler):
|
|
1465
|
-
"""Scroll container
|
|
1465
|
+
"""Scroll container: wraps a single child whose height is unbounded.
|
|
1466
1466
|
|
|
1467
1467
|
The child is positioned by the layout engine using its natural
|
|
1468
1468
|
content height. The shared frame applier expands the parent
|
|
@@ -1713,7 +1713,7 @@ class ImageHandler(IOSViewHandler):
|
|
|
1713
1713
|
|
|
1714
1714
|
|
|
1715
1715
|
# ----------------------------------------------------------------------
|
|
1716
|
-
# TextInput
|
|
1716
|
+
# TextInput: raw libobjc target/delegate
|
|
1717
1717
|
# ----------------------------------------------------------------------
|
|
1718
1718
|
#
|
|
1719
1719
|
# UITextField control events and UITextField/UITextView delegate
|
|
@@ -1786,7 +1786,7 @@ def _tf_on_submit_imp(self_ptr: int, _cmd: int, sender_ptr: int) -> None:
|
|
|
1786
1786
|
|
|
1787
1787
|
|
|
1788
1788
|
def _tf_should_return_imp(self_ptr: int, _cmd: int, tf_ptr: int) -> bool:
|
|
1789
|
-
"""
|
|
1789
|
+
"""Dismiss the keyboard on Return (``textFieldShouldReturn:``).
|
|
1790
1790
|
|
|
1791
1791
|
iOS doesn't dismiss the keyboard on Return by default; the standard
|
|
1792
1792
|
pattern is for the delegate to resign first responder and return
|
|
@@ -2444,7 +2444,7 @@ class ProgressBarHandler(IOSViewHandler):
|
|
|
2444
2444
|
|
|
2445
2445
|
|
|
2446
2446
|
# ======================================================================
|
|
2447
|
-
# WebView
|
|
2447
|
+
# WebView: WKWebView with navigation + script-message delegates
|
|
2448
2448
|
# ======================================================================
|
|
2449
2449
|
|
|
2450
2450
|
# WKWebView.scrollView isn't auto-detected as a property by rubicon, so it
|
|
@@ -2466,7 +2466,7 @@ def _webview_url(webview: Any) -> str:
|
|
|
2466
2466
|
# WKNavigationDelegate + WKScriptMessageHandler bridge. WebKit passes
|
|
2467
2467
|
# object arguments (``WKNavigation*`` / ``WKScriptMessage*``) to these
|
|
2468
2468
|
# delegate callbacks, which rubicon's ``@objc_method`` FFI bridge
|
|
2469
|
-
# mismarshals on iOS 18.x
|
|
2469
|
+
# mismarshals on iOS 18.x; the app dies with EXC_BAD_ACCESS inside
|
|
2470
2470
|
# ``objc_msgSend`` (see the module header note). Like the scroll and
|
|
2471
2471
|
# tab-bar delegates we therefore build the class with raw libobjc and
|
|
2472
2472
|
# CFUNCTYPE IMPs, keep per-delegate state keyed by the delegate
|
|
@@ -2702,7 +2702,7 @@ class SafeAreaViewHandler(IOSViewHandler):
|
|
|
2702
2702
|
|
|
2703
2703
|
|
|
2704
2704
|
# ======================================================================
|
|
2705
|
-
# Modal
|
|
2705
|
+
# Modal: actually presents a UIViewController
|
|
2706
2706
|
# ======================================================================
|
|
2707
2707
|
|
|
2708
2708
|
|
|
@@ -2767,7 +2767,7 @@ class ModalHandler(IOSViewHandler):
|
|
|
2767
2767
|
buf.remove(child)
|
|
2768
2768
|
|
|
2769
2769
|
def set_frame(self, native_view: Any, x: float, y: float, width: float, height: float) -> None:
|
|
2770
|
-
# Modal is a virtual placeholder
|
|
2770
|
+
# Modal is a virtual placeholder, not rendered inline.
|
|
2771
2771
|
return
|
|
2772
2772
|
|
|
2773
2773
|
def measure_intrinsic(
|
|
@@ -2858,7 +2858,7 @@ class ModalHandler(IOSViewHandler):
|
|
|
2858
2858
|
|
|
2859
2859
|
|
|
2860
2860
|
# ======================================================================
|
|
2861
|
-
# StatusBar
|
|
2861
|
+
# StatusBar: global side effect, no view in the tree
|
|
2862
2862
|
# ======================================================================
|
|
2863
2863
|
|
|
2864
2864
|
|
|
@@ -2903,7 +2903,7 @@ class StatusBarHandler(IOSViewHandler):
|
|
|
2903
2903
|
|
|
2904
2904
|
|
|
2905
2905
|
# ======================================================================
|
|
2906
|
-
# KeyboardAvoidingView
|
|
2906
|
+
# KeyboardAvoidingView: publishes the keyboard height to Python
|
|
2907
2907
|
# ======================================================================
|
|
2908
2908
|
|
|
2909
2909
|
|
|
@@ -2976,7 +2976,7 @@ class KeyboardAvoidingViewHandler(IOSViewHandler):
|
|
|
2976
2976
|
|
|
2977
2977
|
|
|
2978
2978
|
# ======================================================================
|
|
2979
|
-
# TabBar
|
|
2979
|
+
# TabBar: UITabBar with a raw ctypes delegate
|
|
2980
2980
|
# ======================================================================
|
|
2981
2981
|
#
|
|
2982
2982
|
# ``tabBar:didSelectItem:`` passes the UITabBarItem as an ObjC object;
|
|
@@ -3193,7 +3193,7 @@ def _present_alert(
|
|
|
3193
3193
|
) -> None:
|
|
3194
3194
|
"""Present a UIAlertController of the given style.
|
|
3195
3195
|
|
|
3196
|
-
Safe to call from any thread
|
|
3196
|
+
Safe to call from any thread; the UIKit work is automatically
|
|
3197
3197
|
marshalled to the main thread via
|
|
3198
3198
|
[`pythonnative.runtime.call_on_main_thread`][pythonnative.runtime.call_on_main_thread].
|
|
3199
3199
|
Returns immediately; the alert appears on the next main-loop tick.
|
|
@@ -3261,7 +3261,7 @@ def _present_alert(
|
|
|
3261
3261
|
|
|
3262
3262
|
|
|
3263
3263
|
# ======================================================================
|
|
3264
|
-
# Picker
|
|
3264
|
+
# Picker: action-sheet dropdown
|
|
3265
3265
|
# ======================================================================
|
|
3266
3266
|
#
|
|
3267
3267
|
# The PythonNative `Picker` renders as a `UIButton` whose tap presents
|
|
@@ -3304,7 +3304,7 @@ def _present_picker_sheet(btn: Any) -> None:
|
|
|
3304
3304
|
|
|
3305
3305
|
|
|
3306
3306
|
class PickerHandler(IOSViewHandler):
|
|
3307
|
-
"""``Picker`` element handler
|
|
3307
|
+
"""``Picker`` element handler, native action-sheet dropdown."""
|
|
3308
3308
|
|
|
3309
3309
|
def _build(self, props: Dict[str, Any]) -> Any:
|
|
3310
3310
|
btn = ObjCClass("UIButton").buttonWithType_(1) # UIButtonTypeSystem
|
|
@@ -3333,7 +3333,7 @@ class PickerHandler(IOSViewHandler):
|
|
|
3333
3333
|
|
|
3334
3334
|
|
|
3335
3335
|
# ======================================================================
|
|
3336
|
-
# Checkbox
|
|
3336
|
+
# Checkbox: SF Symbol UIButton toggling checked / unchecked
|
|
3337
3337
|
# ======================================================================
|
|
3338
3338
|
|
|
3339
3339
|
|
|
@@ -3438,7 +3438,7 @@ class CheckboxHandler(IOSViewHandler):
|
|
|
3438
3438
|
|
|
3439
3439
|
|
|
3440
3440
|
# ======================================================================
|
|
3441
|
-
# SegmentedControl
|
|
3441
|
+
# SegmentedControl: native UISegmentedControl
|
|
3442
3442
|
# ======================================================================
|
|
3443
3443
|
|
|
3444
3444
|
|
|
@@ -3526,7 +3526,7 @@ class SegmentedControlHandler(IOSViewHandler):
|
|
|
3526
3526
|
|
|
3527
3527
|
|
|
3528
3528
|
# ======================================================================
|
|
3529
|
-
# DatePicker
|
|
3529
|
+
# DatePicker: native UIDatePicker (compact style on iOS 13.4+)
|
|
3530
3530
|
# ======================================================================
|
|
3531
3531
|
|
|
3532
3532
|
|
pythonnative/navigation.py
CHANGED
|
@@ -25,7 +25,7 @@ Stack navigators rendered as the root of an app host (i.e. the parent
|
|
|
25
25
|
own handle) talk to the platform via that host's ``_push`` / ``_pop``
|
|
26
26
|
methods, so the back stack matches what UIKit / AndroidX maintain.
|
|
27
27
|
Nested stacks (e.g. a stack inside a tab) fall back to in-Python
|
|
28
|
-
state
|
|
28
|
+
state; there's no second native navigation controller to push
|
|
29
29
|
onto in that case.
|
|
30
30
|
|
|
31
31
|
Example:
|
|
@@ -66,7 +66,7 @@ from .hooks import (
|
|
|
66
66
|
|
|
67
67
|
# Defaults to True: components rendered outside any declarative
|
|
68
68
|
# navigator (e.g. the root component of a screen pushed via the host's
|
|
69
|
-
# native nav stack) are by definition focused
|
|
69
|
+
# native nav stack) are by definition focused; the host's own
|
|
70
70
|
# ``on_resume`` / ``on_pause`` lifecycle drives the focus state for
|
|
71
71
|
# those. Declarative navigators override this provider on the active
|
|
72
72
|
# subtree (always True today; reserved for future inactive-screen
|
|
@@ -152,7 +152,7 @@ class _DeclarativeNavHandle:
|
|
|
152
152
|
|
|
153
153
|
When ``parent`` is the host's own ``NavigationHandle`` (root
|
|
154
154
|
Stack), ``navigate`` / ``go_back`` / ``reset`` drive the native
|
|
155
|
-
navigation controller and the in-Python stack is bypassed
|
|
155
|
+
navigation controller and the in-Python stack is bypassed; the
|
|
156
156
|
OS owns the back-stack source of truth.
|
|
157
157
|
|
|
158
158
|
When ``parent`` is another declarative handle (nested navigator),
|
|
@@ -212,7 +212,7 @@ class _DeclarativeNavHandle:
|
|
|
212
212
|
untouched because the native controller is the source of
|
|
213
213
|
truth.
|
|
214
214
|
- **Nested stack / tab / drawer**: ``set_stack`` is called with
|
|
215
|
-
the new route
|
|
215
|
+
the new route; the parent reconciler re-renders the active
|
|
216
216
|
screen subtree in place.
|
|
217
217
|
- **Unknown route**: forwarded to ``parent`` if one exists,
|
|
218
218
|
otherwise raises ``ValueError``.
|
pythonnative/net.py
CHANGED
|
@@ -4,7 +4,7 @@ A small, dependency-free coroutine wrapper around
|
|
|
4
4
|
:mod:`urllib.request`. Operates on bytes internally and exposes a
|
|
5
5
|
:class:`Response` with `text()`, `json()`, and `bytes` accessors.
|
|
6
6
|
|
|
7
|
-
The implementation is deliberately minimal
|
|
7
|
+
The implementation is deliberately minimal; it covers the
|
|
8
8
|
"call a JSON API" path that's overwhelmingly the use case for mobile
|
|
9
9
|
apps. For streaming, multipart uploads, or HTTP/2, integrate
|
|
10
10
|
``httpx`` / ``aiohttp`` directly; this module won't try to compete.
|
|
@@ -161,7 +161,7 @@ async def fetch(
|
|
|
161
161
|
TimeoutError: If the request doesn't complete within
|
|
162
162
|
``timeout`` seconds.
|
|
163
163
|
OSError: For network errors (DNS failure, connection refused,
|
|
164
|
-
etc.)
|
|
164
|
+
etc.), re-raised from ``urllib``.
|
|
165
165
|
|
|
166
166
|
Example:
|
|
167
167
|
```python
|
|
@@ -223,7 +223,7 @@ def _dispatch_request(request: urllib.request.Request, timeout: float) -> Respon
|
|
|
223
223
|
content=content,
|
|
224
224
|
)
|
|
225
225
|
except urllib.error.HTTPError as exc:
|
|
226
|
-
# HTTPError is itself a response object
|
|
226
|
+
# HTTPError is itself a response object; propagate the body
|
|
227
227
|
# so callers can inspect it before deciding to raise.
|
|
228
228
|
body = exc.read() if hasattr(exc, "read") else b""
|
|
229
229
|
return Response(
|
pythonnative/platform.py
CHANGED
|
@@ -102,7 +102,7 @@ class Platform:
|
|
|
102
102
|
"""Pick the value matching the current platform.
|
|
103
103
|
|
|
104
104
|
Looks up ``spec[Platform.OS]``, then falls back to
|
|
105
|
-
``spec["native"]`` (matches iOS and Android
|
|
105
|
+
``spec["native"]`` (matches iOS and Android, *not* desktop,
|
|
106
106
|
which is a development surface), then to ``spec["default"]``,
|
|
107
107
|
then to the explicit ``default`` argument.
|
|
108
108
|
|
pythonnative/platform_metrics.py
CHANGED
|
@@ -16,7 +16,7 @@ that state to size themselves correctly:
|
|
|
16
16
|
Rather than threading those values through every
|
|
17
17
|
[`measure_intrinsic`][pythonnative.native_views.base.ViewHandler.measure_intrinsic]
|
|
18
18
|
call signature, the screen host writes them here and handlers read
|
|
19
|
-
them on demand. Values are in **dp on Android** and **pt on iOS
|
|
19
|
+
them on demand. Values are in **dp on Android** and **pt on iOS**,
|
|
20
20
|
i.e., the same "layout units" the layout engine uses on each
|
|
21
21
|
platform, so handlers can add them to other layout-unit values
|
|
22
22
|
without further conversion. On iOS the screen host consumes the top
|
|
@@ -88,7 +88,7 @@ def subscribe(callback: Callable[[], None]) -> Callable[[], None]:
|
|
|
88
88
|
|
|
89
89
|
Returns an unsubscribe function. Hooks pass a state setter so a
|
|
90
90
|
component re-renders whenever the platform reports a new value.
|
|
91
|
-
Threadsafe
|
|
91
|
+
Threadsafe: multiple subscribers may register/unregister
|
|
92
92
|
concurrently.
|
|
93
93
|
"""
|
|
94
94
|
with _subscribers_lock:
|
|
@@ -138,7 +138,7 @@ def set_safe_area_insets(top: float, left: float, bottom: float, right: float) -
|
|
|
138
138
|
def get_safe_area_insets() -> SafeAreaInsets:
|
|
139
139
|
"""Return the current safe-area insets.
|
|
140
140
|
|
|
141
|
-
The default value is ``(0, 0, 0, 0)
|
|
141
|
+
The default value is ``(0, 0, 0, 0)``; handlers should still
|
|
142
142
|
function correctly on a desktop / unit-test environment where no
|
|
143
143
|
screen host has published insets.
|
|
144
144
|
"""
|
|
@@ -217,7 +217,7 @@ def reset_keyboard_height() -> None:
|
|
|
217
217
|
# trust ``UITabBar.sizeThatFits_`` (it has historically returned 0 in
|
|
218
218
|
# some configurations) and the screen host deliberately extends the
|
|
219
219
|
# root view past the bottom safe area so the bar reaches the home
|
|
220
|
-
# indicator
|
|
220
|
+
# indicator; both pieces conspire to require a single source of
|
|
221
221
|
# truth for the height formula.
|
|
222
222
|
#
|
|
223
223
|
# Android intentionally has no equivalent: ``BottomNavigationView``
|
|
@@ -240,7 +240,7 @@ def ios_tab_bar_height() -> float:
|
|
|
240
240
|
Equal to ``IOS_TAB_BAR_BASE_HEIGHT_PT + safe_area_insets.bottom``
|
|
241
241
|
so the bar reaches the home indicator. The iOS screen host
|
|
242
242
|
deliberately extends the root view past the bottom safe area for
|
|
243
|
-
this very reason
|
|
243
|
+
this very reason; the tab bar absorbs the inset and UIKit
|
|
244
244
|
renders the pill with internal padding for the home indicator.
|
|
245
245
|
Used by ``pythonnative.native_views.ios.TabBarHandler``; exposed
|
|
246
246
|
here so the formula is testable without importing the iOS
|
pythonnative/preview.py
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
"""Desktop preview runtime
|
|
1
|
+
"""Desktop preview runtime, the engine behind ``pn preview``.
|
|
2
2
|
|
|
3
3
|
``pn preview`` renders a PythonNative app in a real OS window using the
|
|
4
4
|
Tkinter backend ([`pythonnative.native_views.desktop`][pythonnative.native_views.desktop]),
|
|
@@ -50,7 +50,7 @@ class DesktopApp:
|
|
|
50
50
|
[`screen`][pythonnative.screen] host as the ``native_instance`` so
|
|
51
51
|
hosts can drive navigation (``push_screen`` / ``pop_screen`` /
|
|
52
52
|
``reset_to_root``), report the viewport size, and set the window
|
|
53
|
-
title
|
|
53
|
+
title, mirroring the role a ``UIViewController`` / ``Activity``
|
|
54
54
|
plays on device.
|
|
55
55
|
"""
|
|
56
56
|
|
|
@@ -424,7 +424,7 @@ def run_preview(
|
|
|
424
424
|
|
|
425
425
|
root.protocol("WM_DELETE_WINDOW", _on_close)
|
|
426
426
|
root.after(_POLL_INTERVAL_MS, _poll)
|
|
427
|
-
print(f"[pn preview] {component_path}
|
|
427
|
+
print(f"[pn preview] {component_path} ({int(width)}x{int(height)})", file=sys.stderr)
|
|
428
428
|
try:
|
|
429
429
|
root.mainloop()
|
|
430
430
|
except KeyboardInterrupt:
|
pythonnative/project/builder.py
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
The [`Builder`][pythonnative.project.builder.Builder] ties the pieces
|
|
4
4
|
together: it stages the bundled native template, runs the platform
|
|
5
5
|
[`configurators`][pythonnative.project.android], invokes the native
|
|
6
|
-
toolchains (Gradle / Xcode), and
|
|
6
|
+
toolchains (Gradle / Xcode), and, on iOS, embeds the CPython runtime into
|
|
7
7
|
the built app.
|
|
8
8
|
|
|
9
9
|
All shell-outs go through a small
|
pythonnative/project/config.py
CHANGED
|
@@ -113,7 +113,7 @@ class IOSSigning:
|
|
|
113
113
|
"""iOS code-signing / export configuration (``[ios.signing]``).
|
|
114
114
|
|
|
115
115
|
Attributes:
|
|
116
|
-
export_method: How the archive is exported
|
|
116
|
+
export_method: How the archive is exported, one of
|
|
117
117
|
``development``, ``ad-hoc``, ``app-store``, ``enterprise``.
|
|
118
118
|
provisioning_profile: Optional provisioning profile name or UUID
|
|
119
119
|
for manual signing.
|
|
@@ -590,7 +590,7 @@ def render_default_toml(*, name: str, app_id: str, python_version: str = "3.11")
|
|
|
590
590
|
"""
|
|
591
591
|
display = name.replace("_", " ").replace("-", " ").strip().title() or name
|
|
592
592
|
return f"""# PythonNative project configuration.
|
|
593
|
-
# Docs: https://
|
|
593
|
+
# Docs: https://pythonnative.com/guides/configuration/
|
|
594
594
|
|
|
595
595
|
[app]
|
|
596
596
|
id = "{app_id}"
|
|
@@ -604,7 +604,7 @@ entry_point = "app/main.py"
|
|
|
604
604
|
|
|
605
605
|
# Declare the device capabilities your app needs. A string becomes the
|
|
606
606
|
# iOS permission prompt text; `true` uses a sensible default.
|
|
607
|
-
# See: https://
|
|
607
|
+
# See: https://pythonnative.com/guides/permissions/
|
|
608
608
|
[permissions]
|
|
609
609
|
# camera = "Scan receipts with your camera."
|
|
610
610
|
# location_when_in_use = "Show nearby stores."
|
pythonnative/project/doctor.py
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"""Environment diagnostics for ``pn doctor``.
|
|
2
2
|
|
|
3
3
|
Inspects the local toolchain and the project's ``pythonnative.toml`` and
|
|
4
|
-
reports what's ready and what's missing for building on each platform
|
|
4
|
+
reports what's ready and what's missing for building on each platform,
|
|
5
5
|
analogous to ``flutter doctor`` / ``npx react-native doctor``. The checks
|
|
6
6
|
are deliberately read-only and fast; they shell out only to ask tools for
|
|
7
7
|
their versions.
|
|
@@ -44,7 +44,7 @@ class CheckResult:
|
|
|
44
44
|
def format(self) -> str:
|
|
45
45
|
"""Return a single aligned line for terminal output."""
|
|
46
46
|
symbol = _SYMBOLS.get(self.level, "[?]")
|
|
47
|
-
suffix = f"
|
|
47
|
+
suffix = f": {self.detail}" if self.detail else ""
|
|
48
48
|
return f" {symbol} {self.name}{suffix}"
|
|
49
49
|
|
|
50
50
|
|
pythonnative/project/icons.py
CHANGED
|
@@ -77,7 +77,7 @@ def _circular(img: "object") -> "object":
|
|
|
77
77
|
def generate_ios_icons(source: Path, appiconset_dir: Path) -> bool:
|
|
78
78
|
"""Generate a single-size iOS ``AppIcon.appiconset``.
|
|
79
79
|
|
|
80
|
-
Writes ``icon-1024.png`` (a flattened, opaque 1024x1024 image
|
|
80
|
+
Writes ``icon-1024.png`` (a flattened, opaque 1024x1024 image; the
|
|
81
81
|
App Store rejects icons with alpha) and a ``Contents.json`` that
|
|
82
82
|
declares it as the universal iOS app icon. Xcode derives every other
|
|
83
83
|
size at build time.
|
pythonnative/project/ios.py
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
Adapts the bundled ``ios_template`` Xcode project to a specific
|
|
4
4
|
[`AppConfig`][pythonnative.project.config.AppConfig]. Unlike Android, the
|
|
5
5
|
iOS bundle identifier, version, team, and deployment target are *not*
|
|
6
|
-
baked into files
|
|
6
|
+
baked into files; they're passed to ``xcodebuild`` as build-setting
|
|
7
7
|
overrides (see [`build_settings`][pythonnative.project.ios.build_settings]),
|
|
8
8
|
which avoids fragile ``project.pbxproj`` edits. This module owns the
|
|
9
9
|
parts that must live on disk:
|
|
@@ -23,7 +23,7 @@ artifacts it requires:
|
|
|
23
23
|
A capability's value may be either a string (used verbatim as the iOS
|
|
24
24
|
usage description) or ``true`` (use the capability's
|
|
25
25
|
[`default_reason`][pythonnative.project.permissions.Capability]). A value
|
|
26
|
-
of ``false`` disables the capability
|
|
26
|
+
of ``false`` disables the capability, useful for switching one off
|
|
27
27
|
without deleting the line.
|
|
28
28
|
|
|
29
29
|
The catalog is the single source of truth shared by the iOS and Android
|
|
@@ -284,7 +284,7 @@ def resolve_permissions(
|
|
|
284
284
|
"""Resolve a declared capability map into native permission artifacts.
|
|
285
285
|
|
|
286
286
|
Args:
|
|
287
|
-
permissions: The ``[permissions]`` table
|
|
287
|
+
permissions: The ``[permissions]`` table, capability key to a
|
|
288
288
|
reason string or boolean. ``false``/``None`` values are
|
|
289
289
|
skipped (capability disabled).
|
|
290
290
|
extra_android_permissions: Additional raw Android permission
|
|
@@ -9,8 +9,8 @@ paths the [`ios`][pythonnative.project.ios] configurator needs:
|
|
|
9
9
|
``Python.xcframework``, the simulator ``Python.framework``, the standard
|
|
10
10
|
library, and the simulator headers/static lib.
|
|
11
11
|
|
|
12
|
-
Android doesn't need any of this
|
|
13
|
-
Gradle
|
|
12
|
+
Android doesn't need any of this: Chaquopy ships its own CPython via
|
|
13
|
+
Gradle, so there's no Android equivalent here.
|
|
14
14
|
"""
|
|
15
15
|
|
|
16
16
|
from __future__ import annotations
|
|
@@ -238,7 +238,7 @@ def prepare_ios_runtime(
|
|
|
238
238
|
try:
|
|
239
239
|
return _locate_runtime(extract_root, python_version)
|
|
240
240
|
except RuntimeError:
|
|
241
|
-
# Stale/partial extraction
|
|
241
|
+
# Stale/partial extraction: re-extract below.
|
|
242
242
|
pass
|
|
243
243
|
|
|
244
244
|
url = resolve_asset_url(python_version, preferred_name=preferred_name)
|
pythonnative/reconciler.py
CHANGED
|
@@ -9,7 +9,7 @@ The diff phase is *pure*: it never touches the native layer. Each pass
|
|
|
9
9
|
appends ops (`pythonnative.mutations`) to a transaction list, and the
|
|
10
10
|
commit applies them through a single
|
|
11
11
|
``backend.apply_mutations(ops)`` call. Event callbacks never cross into
|
|
12
|
-
the native layer at all
|
|
12
|
+
the native layer at all; they live in the Python-side
|
|
13
13
|
[`EventRegistry`][pythonnative.events.EventRegistry], keyed by tag, so
|
|
14
14
|
re-renders that only produce fresh closures cost nothing natively.
|
|
15
15
|
|
|
@@ -66,7 +66,7 @@ def _shallow_equal_props(old: dict, new: dict) -> bool:
|
|
|
66
66
|
|
|
67
67
|
Used by [`memo`][pythonnative.memo] to skip re-rendering when none
|
|
68
68
|
of a component's props changed identity. Callables only count as
|
|
69
|
-
equal if they're the *same object
|
|
69
|
+
equal if they're the *same object*; fresh closures always invalidate
|
|
70
70
|
the memo (matching React's behavior; pair with
|
|
71
71
|
[`use_callback`][pythonnative.use_callback] when stability matters).
|
|
72
72
|
"""
|
|
@@ -173,7 +173,7 @@ class VNode:
|
|
|
173
173
|
# node's props change, so unchanged leaves skip native
|
|
174
174
|
# ``measure_intrinsic`` calls entirely.
|
|
175
175
|
self._measure_cache: Optional[Tuple[float, float, float, float]] = None
|
|
176
|
-
# Last frame sent to the native side
|
|
176
|
+
# Last frame sent to the native side; frames that don't change
|
|
177
177
|
# are skipped (frame diffing).
|
|
178
178
|
self._last_frame: Optional[Tuple[float, float, float, float]] = None
|
|
179
179
|
# Cached LayoutNode reused across passes while the subtree is
|
|
@@ -444,8 +444,8 @@ class Reconciler:
|
|
|
444
444
|
Unlike a full reconcile from the root, a local update starts in
|
|
445
445
|
the *middle* of the tree, so the context stack of every
|
|
446
446
|
``__Provider__`` ancestor must be re-established before the body
|
|
447
|
-
runs (otherwise [`use_context`][pythonnative.use_context]
|
|
448
|
-
therefore [`use_navigation`][pythonnative.use_navigation]
|
|
447
|
+
runs (otherwise [`use_context`][pythonnative.use_context], and
|
|
448
|
+
therefore [`use_navigation`][pythonnative.use_navigation], would
|
|
449
449
|
read the context default instead of the provided value). Nested
|
|
450
450
|
providers *inside* this subtree are pushed/popped normally by the
|
|
451
451
|
recursive reconcile beneath us.
|
|
@@ -939,7 +939,7 @@ class Reconciler:
|
|
|
939
939
|
node.hook_state.cleanup_all_effects()
|
|
940
940
|
# Break the back-references so the unmounted component's hook
|
|
941
941
|
# state (and the closures it captured) can be freed by plain
|
|
942
|
-
# refcounting
|
|
942
|
+
# refcounting, important on iOS, where the cyclic GC is
|
|
943
943
|
# disabled.
|
|
944
944
|
node.hook_state._vnode = None
|
|
945
945
|
node.hook_state._reconciler = None
|
|
@@ -983,7 +983,7 @@ class Reconciler:
|
|
|
983
983
|
|
|
984
984
|
Event callables never appear here (they live in the event
|
|
985
985
|
registry), so listener identity churn produces no native
|
|
986
|
-
traffic
|
|
986
|
+
traffic; only the ``_pn_events`` name set is compared.
|
|
987
987
|
"""
|
|
988
988
|
changed = {}
|
|
989
989
|
for key, new_val in new.items():
|
|
@@ -1056,7 +1056,7 @@ class Reconciler:
|
|
|
1056
1056
|
"""Whether ``changed`` props can alter the node's layout.
|
|
1057
1057
|
|
|
1058
1058
|
Content-sized leaves re-measure on *any* prop change (text,
|
|
1059
|
-
font, image source
|
|
1059
|
+
font, image source: almost everything affects their intrinsic
|
|
1060
1060
|
size). Containers only care about the layout style keys.
|
|
1061
1061
|
"""
|
|
1062
1062
|
if type_name in cls._INTRINSIC_TYPES:
|
|
@@ -1110,7 +1110,7 @@ class Reconciler:
|
|
|
1110
1110
|
)
|
|
1111
1111
|
viewport.dirty = True
|
|
1112
1112
|
calculate_layout(viewport, viewport_w, viewport_h)
|
|
1113
|
-
# Skip set_frame for the root itself
|
|
1113
|
+
# Skip set_frame for the root itself; descendants are
|
|
1114
1114
|
# positioned relative to the root's local origin, which is
|
|
1115
1115
|
# what they want regardless of where the host placed the
|
|
1116
1116
|
# root in the screen.
|
pythonnative/runtime.py
CHANGED
|
@@ -2,7 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
PythonNative runs a single, framework-wide ``asyncio`` event loop on
|
|
4
4
|
a dedicated daemon thread. Every awaitable surface in the framework
|
|
5
|
-
|
|
5
|
+
schedules its work on this loop via
|
|
6
|
+
[`run_async`][pythonnative.runtime.run_async]: the async hooks
|
|
6
7
|
([`use_async_effect`][pythonnative.hooks.use_async_effect],
|
|
7
8
|
[`use_query`][pythonnative.hooks.use_query],
|
|
8
9
|
[`use_mutation`][pythonnative.hooks.use_mutation]), the
|
|
@@ -12,8 +13,7 @@ native modules
|
|
|
12
13
|
([`Camera`][pythonnative.native_modules.camera.Camera] /
|
|
13
14
|
[`Location`][pythonnative.native_modules.location.Location] /
|
|
14
15
|
[`Notifications`][pythonnative.native_modules.notifications.Notifications]),
|
|
15
|
-
and awaited animations
|
|
16
|
-
[`run_async`][pythonnative.runtime.run_async].
|
|
16
|
+
and awaited animations.
|
|
17
17
|
|
|
18
18
|
The reconciler is **not** asyncio-aware; it still runs synchronously on
|
|
19
19
|
the platform main thread. Coroutines that want to mutate component
|
|
@@ -66,7 +66,7 @@ _lock = threading.Lock()
|
|
|
66
66
|
# ======================================================================
|
|
67
67
|
#
|
|
68
68
|
# By default ``threading.Thread`` lands at a low QoS class on Apple
|
|
69
|
-
# platforms, which iOS subjects to wake-up coalescing
|
|
69
|
+
# platforms, which iOS subjects to wake-up coalescing; the asyncio
|
|
70
70
|
# loop only gets ~2 timeslices per second (~500ms granularity). Bumping
|
|
71
71
|
# the thread to ``QOS_CLASS_USER_INTERACTIVE`` is *necessary* for it to
|
|
72
72
|
# be treated as foreground work, but on the simulator it's not
|
|
@@ -85,7 +85,7 @@ def _apply_apple_thread_qos() -> None:
|
|
|
85
85
|
No-op on other platforms or if the symbol can't be loaded. Must be
|
|
86
86
|
called from inside the target thread (the underlying syscall is
|
|
87
87
|
``pthread_set_qos_class_self_np``). Empirically ``USER_INTERACTIVE``
|
|
88
|
-
is required on iOS
|
|
88
|
+
is required on iOS; anything lower triggers wake-up coalescing on
|
|
89
89
|
the background asyncio thread, which adds ~500ms latency to every
|
|
90
90
|
cross-thread dispatch.
|
|
91
91
|
"""
|
|
@@ -151,7 +151,7 @@ def _shutdown_for_tests() -> None:
|
|
|
151
151
|
Cancels every pending task, stops the loop, joins the thread, and
|
|
152
152
|
clears the module-level state so the next call to
|
|
153
153
|
[`get_loop`][pythonnative.runtime.get_loop] starts a fresh loop.
|
|
154
|
-
Production code should not call this
|
|
154
|
+
Production code should not call this; the loop is a daemon and
|
|
155
155
|
will be torn down with the process.
|
|
156
156
|
"""
|
|
157
157
|
global _loop, _thread
|
|
@@ -316,7 +316,7 @@ def call_on_main_thread(fn: Callable[[], None]) -> None:
|
|
|
316
316
|
|
|
317
317
|
- **iOS**: dispatches ``fn`` onto the main dispatch queue via
|
|
318
318
|
``libdispatch.dispatch_async_f`` (called through
|
|
319
|
-
:class:`ctypes.PyDLL` to keep the GIL held
|
|
319
|
+
:class:`ctypes.PyDLL` to keep the GIL held; see the
|
|
320
320
|
``_ios_call_on_main`` comment block for why this matters).
|
|
321
321
|
- **Android**: posts a ``Runnable`` to
|
|
322
322
|
``Handler(Looper.getMainLooper())``.
|
|
@@ -469,7 +469,7 @@ def _ios_call_on_main(fn: Callable[[], None]) -> None:
|
|
|
469
469
|
_main_next_id += 1
|
|
470
470
|
key = _main_next_id
|
|
471
471
|
_main_pending[key] = fn
|
|
472
|
-
# dispatch_async_f(queue, context, work)
|
|
472
|
+
# dispatch_async_f(queue, context, work): non-blocking; just
|
|
473
473
|
# enqueues the work onto the main queue and returns.
|
|
474
474
|
assert _dispatch_async_f_c is not None
|
|
475
475
|
_dispatch_async_f_c(_dispatch_main_q_ptr, key, _main_trampoline_c)
|
pythonnative/screen.py
CHANGED
|
@@ -186,7 +186,7 @@ def _init_host_common(host: Any, component_path: str, component_func: Any) -> No
|
|
|
186
186
|
host._hot_reload_manifest_path = None
|
|
187
187
|
host._hot_reload_last_version = None
|
|
188
188
|
host._layout_listener = None # retained on Android to prevent GC
|
|
189
|
-
# Focus state
|
|
189
|
+
# Focus state: drives ``use_focus_effect``. Starts focused because
|
|
190
190
|
# a host is only created when the screen is being presented; the
|
|
191
191
|
# platform lifecycle hooks (``on_resume`` / ``on_pause``) flip this
|
|
192
192
|
# when the user navigates to / from another screen.
|
|
@@ -276,14 +276,14 @@ def _on_create(host: Any) -> None:
|
|
|
276
276
|
# Android the FragmentManager destroys and recreates a screen's
|
|
277
277
|
# view every time the user pops back to it, and the platform
|
|
278
278
|
# template calls ``screen.on_create()`` again from
|
|
279
|
-
# ``onViewCreated
|
|
279
|
+
# ``onViewCreated``, but the Python screen object (and therefore
|
|
280
280
|
# the reconciler, hook state, focus subscribers, etc.) persists
|
|
281
281
|
# across that. Re-running the full mount path here would reset
|
|
282
282
|
# use_state, clobber use_focus_effect subscriptions, and break
|
|
283
283
|
# navigation handles held by existing components, which is why
|
|
284
284
|
# the focus counter never advanced past ``1`` before this guard.
|
|
285
285
|
# If we're already mounted, just re-attach the existing root view
|
|
286
|
-
# to the (newly created) native container
|
|
286
|
+
# to the (newly created) native container; ``on_resume`` will
|
|
287
287
|
# fire the focus subscribers separately.
|
|
288
288
|
if host._reconciler is not None and host._root_native_view is not None:
|
|
289
289
|
host._attach_root(host._root_native_view)
|
|
@@ -728,7 +728,7 @@ if IS_ANDROID:
|
|
|
728
728
|
# Publish insets first so the very first layout pass sees
|
|
729
729
|
# them. Otherwise handlers reading insets at first paint
|
|
730
730
|
# would get ``(0, 0, 0, 0)`` and re-measure once the
|
|
731
|
-
# ``OnLayoutChangeListener`` fires moments later
|
|
731
|
+
# ``OnLayoutChangeListener`` fires moments later, a
|
|
732
732
|
# measurable flicker (~50–200 ms on a stock Pixel
|
|
733
733
|
# emulator).
|
|
734
734
|
_android_publish_window_insets(view)
|
|
@@ -1089,7 +1089,7 @@ else:
|
|
|
1089
1089
|
the concrete type varies between releases:
|
|
1090
1090
|
|
|
1091
1091
|
- On rubicon-objc 0.5.x ``ptr`` is ``bytes`` (the raw 8-byte,
|
|
1092
|
-
little-endian address)
|
|
1092
|
+
little-endian address); ``int(ptr)`` raises ``ValueError``
|
|
1093
1093
|
because Python tries to parse the bytes as a decimal string.
|
|
1094
1094
|
- Older releases return a ``c_void_p`` for which ``int(ptr)``
|
|
1095
1095
|
works.
|
pythonnative/sdk/_components.py
CHANGED
|
@@ -361,7 +361,7 @@ def element_factory(name: str) -> Callable[..., Element]:
|
|
|
361
361
|
arguments matching the registered props dataclass.
|
|
362
362
|
|
|
363
363
|
If no ``props`` dataclass was registered for ``name``, kwargs flow
|
|
364
|
-
through unmodified
|
|
364
|
+
through unmodified, useful when iterating before locking down a
|
|
365
365
|
prop schema.
|
|
366
366
|
|
|
367
367
|
Args:
|