pythonnative 0.14.0__py3-none-any.whl → 0.15.0__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 CHANGED
@@ -23,8 +23,18 @@ Key building blocks:
23
23
  factories.
24
24
  - **Styling** uses a single ``style`` dict per element (or a list of
25
25
  dicts), composable via [`StyleSheet`][pythonnative.StyleSheet].
26
+ PythonNative ships a fully-typed [`Style`][pythonnative.style.Style]
27
+ TypedDict so editors and ``mypy`` validate every key as you type.
26
28
  - **Animations** use the ``Animated`` namespace, modeled on React
27
29
  Native's animation API.
30
+ - **Custom native components** can be authored with the
31
+ ``pythonnative.sdk`` package: define a typed
32
+ [`Props`][pythonnative.sdk.Props] dataclass, implement a
33
+ [`ViewHandler`][pythonnative.native_views.base.ViewHandler] for each
34
+ platform, and register it via
35
+ [`@native_component`][pythonnative.sdk.native_component] (or expose
36
+ it from a PyPI package via the ``pythonnative.handlers`` entry-point
37
+ group).
28
38
 
29
39
  Example:
30
40
  ```python
@@ -34,15 +44,16 @@ Example:
34
44
  def App():
35
45
  count, set_count = pn.use_state(0)
36
46
  return pn.Column(
37
- pn.Text(f"Count: {count}", style={"font_size": 24}),
47
+ pn.Text(f"Count: {count}", style=pn.style(font_size=24)),
38
48
  pn.Button("+", on_click=lambda: set_count(count + 1)),
39
- style={"spacing": 12},
49
+ style=pn.style(spacing=12),
40
50
  )
41
51
  ```
42
52
  """
43
53
 
44
- __version__ = "0.14.0"
54
+ __version__ = "0.15.0"
45
55
 
56
+ from . import sdk
46
57
  from .alerts import Alert
47
58
  from .animated import Animated, AnimatedValue
48
59
  from .components import (
@@ -100,7 +111,39 @@ from .navigation import (
100
111
  )
101
112
  from .platform import Platform
102
113
  from .screen import create_screen
103
- from .style import StyleSheet, ThemeContext
114
+ from .sdk import (
115
+ Props,
116
+ ViewHandler,
117
+ element_factory,
118
+ native_component,
119
+ register_component,
120
+ )
121
+ from .style import (
122
+ AlignItems,
123
+ AlignSelf,
124
+ AutoCapitalize,
125
+ Color,
126
+ Dimension,
127
+ EdgeInsets,
128
+ FlexDirection,
129
+ FontWeight,
130
+ JustifyContent,
131
+ KeyboardType,
132
+ Overflow,
133
+ Position,
134
+ ReturnKeyType,
135
+ ScaleType,
136
+ ShadowOffset,
137
+ Style,
138
+ StyleProp,
139
+ StyleSheet,
140
+ TextAlign,
141
+ TextDecoration,
142
+ ThemeContext,
143
+ TransformSpec,
144
+ resolve_style,
145
+ style,
146
+ )
104
147
 
105
148
  __all__ = [
106
149
  # Components
@@ -154,9 +197,31 @@ __all__ = [
154
197
  "create_drawer_navigator",
155
198
  "create_stack_navigator",
156
199
  "create_tab_navigator",
157
- # Styling
200
+ # Styling - typed primitives
201
+ "AlignItems",
202
+ "AlignSelf",
203
+ "AutoCapitalize",
204
+ "Color",
205
+ "Dimension",
206
+ "EdgeInsets",
207
+ "FlexDirection",
208
+ "FontWeight",
209
+ "JustifyContent",
210
+ "KeyboardType",
211
+ "Overflow",
212
+ "Position",
213
+ "ReturnKeyType",
214
+ "ScaleType",
215
+ "ShadowOffset",
216
+ "Style",
217
+ "StyleProp",
158
218
  "StyleSheet",
219
+ "TextAlign",
220
+ "TextDecoration",
159
221
  "ThemeContext",
222
+ "TransformSpec",
223
+ "resolve_style",
224
+ "style",
160
225
  # Animation
161
226
  "Animated",
162
227
  "AnimatedValue",
@@ -169,4 +234,11 @@ __all__ = [
169
234
  "Notifications",
170
235
  # Platform
171
236
  "Platform",
237
+ # Custom-component SDK
238
+ "Props",
239
+ "ViewHandler",
240
+ "element_factory",
241
+ "native_component",
242
+ "register_component",
243
+ "sdk",
172
244
  ]
pythonnative/animated.py CHANGED
@@ -58,7 +58,7 @@ from typing import Any, Callable, Dict, List, Optional, Tuple
58
58
 
59
59
  from .element import Element
60
60
  from .hooks import use_effect, use_ref
61
- from .style import StyleValue, resolve_style
61
+ from .style import StyleProp, resolve_style
62
62
 
63
63
  # Maximum frame rate at which the Python ticker drives animations.
64
64
  # We aim for 60 Hz but back off when no animation is active.
@@ -433,7 +433,7 @@ class _AnimationHandle:
433
433
  # ======================================================================
434
434
 
435
435
 
436
- def _resolve_style_with_values(style: StyleValue) -> Tuple[Dict[str, Any], Dict[str, AnimatedValue]]:
436
+ def _resolve_style_with_values(style: StyleProp) -> Tuple[Dict[str, Any], Dict[str, AnimatedValue]]:
437
437
  """Return ``(plain_style, animated_bindings)``.
438
438
 
439
439
  AnimatedValue entries in the style are replaced with their
@@ -35,10 +35,18 @@ Example:
35
35
  ```
36
36
  """
37
37
 
38
- from typing import Any, Callable, Dict, List, Optional
38
+ from typing import Any, Callable, Dict, List, Literal, Optional
39
39
 
40
40
  from .element import Element
41
- from .style import StyleValue, resolve_style
41
+ from .style import (
42
+ AutoCapitalize,
43
+ Color,
44
+ KeyboardType,
45
+ ReturnKeyType,
46
+ ScaleType,
47
+ StyleProp,
48
+ resolve_style,
49
+ )
42
50
 
43
51
  # ======================================================================
44
52
  # Leaf components
@@ -73,7 +81,7 @@ def _accessibility_props(
73
81
  def Text(
74
82
  text: str = "",
75
83
  *,
76
- style: StyleValue = None,
84
+ style: StyleProp = None,
77
85
  accessibility_label: Optional[str] = None,
78
86
  accessibility_hint: Optional[str] = None,
79
87
  accessibility_role: Optional[str] = None,
@@ -118,7 +126,7 @@ def Button(
118
126
  *,
119
127
  on_click: Optional[Callable[[], None]] = None,
120
128
  enabled: bool = True,
121
- style: StyleValue = None,
129
+ style: StyleProp = None,
122
130
  accessibility_label: Optional[str] = None,
123
131
  accessibility_hint: Optional[str] = None,
124
132
  accessible: Optional[bool] = None,
@@ -174,14 +182,14 @@ def TextInput(
174
182
  on_submit: Optional[Callable[[str], None]] = None,
175
183
  secure: bool = False,
176
184
  multiline: bool = False,
177
- keyboard_type: Optional[str] = None,
178
- auto_capitalize: Optional[str] = None,
185
+ keyboard_type: Optional[KeyboardType] = None,
186
+ auto_capitalize: Optional[AutoCapitalize] = None,
179
187
  auto_correct: Optional[bool] = None,
180
188
  auto_focus: bool = False,
181
- return_key_type: Optional[str] = None,
189
+ return_key_type: Optional[ReturnKeyType] = None,
182
190
  max_length: Optional[int] = None,
183
- placeholder_color: Optional[str] = None,
184
- style: StyleValue = None,
191
+ placeholder_color: Optional[Color] = None,
192
+ style: StyleProp = None,
185
193
  accessibility_label: Optional[str] = None,
186
194
  accessibility_hint: Optional[str] = None,
187
195
  accessible: Optional[bool] = None,
@@ -257,9 +265,9 @@ def TextInput(
257
265
  def Image(
258
266
  source: str = "",
259
267
  *,
260
- scale_type: Optional[str] = None,
261
- tint_color: Optional[str] = None,
262
- style: StyleValue = None,
268
+ scale_type: Optional[ScaleType] = None,
269
+ tint_color: Optional[Color] = None,
270
+ style: StyleProp = None,
263
271
  accessibility_label: Optional[str] = None,
264
272
  accessible: Optional[bool] = None,
265
273
  ref: Optional[Dict[str, Any]] = None,
@@ -307,7 +315,7 @@ def Switch(
307
315
  *,
308
316
  value: bool = False,
309
317
  on_change: Optional[Callable[[bool], None]] = None,
310
- style: StyleValue = None,
318
+ style: StyleProp = None,
311
319
  key: Optional[str] = None,
312
320
  ) -> Element:
313
321
  """Display a toggle switch.
@@ -331,7 +339,7 @@ def Switch(
331
339
  def ProgressBar(
332
340
  *,
333
341
  value: float = 0.0,
334
- style: StyleValue = None,
342
+ style: StyleProp = None,
335
343
  key: Optional[str] = None,
336
344
  ) -> Element:
337
345
  """Show determinate progress as a value between 0.0 and 1.0.
@@ -356,7 +364,7 @@ def ProgressBar(
356
364
  def ActivityIndicator(
357
365
  *,
358
366
  animating: bool = True,
359
- style: StyleValue = None,
367
+ style: StyleProp = None,
360
368
  key: Optional[str] = None,
361
369
  ) -> Element:
362
370
  """Show an indeterminate loading spinner.
@@ -378,7 +386,7 @@ def ActivityIndicator(
378
386
  def WebView(
379
387
  *,
380
388
  url: str = "",
381
- style: StyleValue = None,
389
+ style: StyleProp = None,
382
390
  key: Optional[str] = None,
383
391
  ) -> Element:
384
392
  """Embed web content from a URL.
@@ -440,7 +448,7 @@ def Slider(
440
448
  min_value: float = 0.0,
441
449
  max_value: float = 1.0,
442
450
  on_change: Optional[Callable[[float], None]] = None,
443
- style: StyleValue = None,
451
+ style: StyleProp = None,
444
452
  key: Optional[str] = None,
445
453
  ) -> Element:
446
454
  """Continuous-value slider between `min_value` and `max_value`.
@@ -475,7 +483,7 @@ def Slider(
475
483
 
476
484
  def View(
477
485
  *children: Element,
478
- style: StyleValue = None,
486
+ style: StyleProp = None,
479
487
  accessibility_label: Optional[str] = None,
480
488
  accessibility_hint: Optional[str] = None,
481
489
  accessibility_role: Optional[str] = None,
@@ -530,7 +538,7 @@ def View(
530
538
 
531
539
  def Column(
532
540
  *children: Element,
533
- style: StyleValue = None,
541
+ style: StyleProp = None,
534
542
  ref: Optional[Dict[str, Any]] = None,
535
543
  key: Optional[str] = None,
536
544
  ) -> Element:
@@ -571,7 +579,7 @@ def Column(
571
579
 
572
580
  def Row(
573
581
  *children: Element,
574
- style: StyleValue = None,
582
+ style: StyleProp = None,
575
583
  ref: Optional[Dict[str, Any]] = None,
576
584
  key: Optional[str] = None,
577
585
  ) -> Element:
@@ -614,7 +622,7 @@ def ScrollView(
614
622
  child: Optional[Element] = None,
615
623
  *,
616
624
  refresh_control: Optional[Dict[str, Any]] = None,
617
- style: StyleValue = None,
625
+ style: StyleProp = None,
618
626
  ref: Optional[Dict[str, Any]] = None,
619
627
  key: Optional[str] = None,
620
628
  ) -> Element:
@@ -647,7 +655,7 @@ def ScrollView(
647
655
 
648
656
  def SafeAreaView(
649
657
  *children: Element,
650
- style: StyleValue = None,
658
+ style: StyleProp = None,
651
659
  key: Optional[str] = None,
652
660
  ) -> Element:
653
661
  """Container that respects safe-area insets (notch, status bar, home indicator).
@@ -670,9 +678,9 @@ def Modal(
670
678
  visible: bool = False,
671
679
  on_dismiss: Optional[Callable[[], None]] = None,
672
680
  title: Optional[str] = None,
673
- animation_type: str = "slide",
681
+ animation_type: Literal["slide", "fade", "none"] = "slide",
674
682
  transparent: bool = False,
675
- style: StyleValue = None,
683
+ style: StyleProp = None,
676
684
  key: Optional[str] = None,
677
685
  ) -> Element:
678
686
  """Overlay modal dialog backed by a real native presentation.
@@ -720,7 +728,7 @@ def Pressable(
720
728
  on_press: Optional[Callable[[], None]] = None,
721
729
  on_long_press: Optional[Callable[[], None]] = None,
722
730
  pressed_opacity: float = 0.6,
723
- style: StyleValue = None,
731
+ style: StyleProp = None,
724
732
  accessibility_label: Optional[str] = None,
725
733
  accessibility_hint: Optional[str] = None,
726
734
  accessible: Optional[bool] = None,
@@ -811,7 +819,7 @@ def FlatList(
811
819
  separator_height: float = 0,
812
820
  refresh_control: Optional[Dict[str, Any]] = None,
813
821
  on_item_press: Optional[Callable[[int], None]] = None,
814
- style: StyleValue = None,
822
+ style: StyleProp = None,
815
823
  key: Optional[str] = None,
816
824
  ) -> Element:
817
825
  """Virtualized scrollable list that renders items from `data` lazily.
@@ -941,7 +949,7 @@ def SectionList(
941
949
  item_height: Optional[float] = None,
942
950
  section_header_height: float = 32.0,
943
951
  separator_height: float = 0,
944
- style: StyleValue = None,
952
+ style: StyleProp = None,
945
953
  key: Optional[str] = None,
946
954
  ) -> Element:
947
955
  """Virtualized list that supports section headers.
@@ -1040,8 +1048,8 @@ def SectionList(
1040
1048
 
1041
1049
  def StatusBar(
1042
1050
  *,
1043
- style: Optional[str] = None,
1044
- background_color: Optional[str] = None,
1051
+ style: Optional[Literal["light", "dark", "default"]] = None,
1052
+ background_color: Optional[Color] = None,
1045
1053
  hidden: Optional[bool] = None,
1046
1054
  key: Optional[str] = None,
1047
1055
  ) -> Element:
@@ -1075,8 +1083,8 @@ def StatusBar(
1075
1083
 
1076
1084
  def KeyboardAvoidingView(
1077
1085
  *children: Element,
1078
- behavior: str = "padding",
1079
- style: StyleValue = None,
1086
+ behavior: Literal["padding", "position"] = "padding",
1087
+ style: StyleProp = None,
1080
1088
  key: Optional[str] = None,
1081
1089
  ) -> Element:
1082
1090
  """Wrap content that should shift up when the keyboard is shown.
@@ -1106,7 +1114,7 @@ def RefreshControl(
1106
1114
  *,
1107
1115
  refreshing: bool = False,
1108
1116
  on_refresh: Optional[Callable[[], None]] = None,
1109
- tint_color: Optional[str] = None,
1117
+ tint_color: Optional[Color] = None,
1110
1118
  ) -> Dict[str, Any]:
1111
1119
  """Pull-to-refresh spec for [`ScrollView`][pythonnative.ScrollView] / [`FlatList`][pythonnative.FlatList].
1112
1120
 
@@ -1162,7 +1170,7 @@ def Picker(
1162
1170
  items: Optional[List[Dict[str, Any]]] = None,
1163
1171
  on_change: Optional[Callable[[Any], None]] = None,
1164
1172
  placeholder: str = "Select…",
1165
- style: StyleValue = None,
1173
+ style: StyleProp = None,
1166
1174
  key: Optional[str] = None,
1167
1175
  ) -> Element:
1168
1176
  """A select / dropdown widget.
@@ -237,12 +237,50 @@ class NativeViewRegistry:
237
237
  _registry: Optional[NativeViewRegistry] = None
238
238
 
239
239
 
240
+ def _active_platform_name() -> str:
241
+ """Return ``"android"`` or ``"ios"`` for the active runtime."""
242
+ from ..utils import IS_ANDROID
243
+
244
+ return "android" if IS_ANDROID else "ios"
245
+
246
+
247
+ def _register_builtin_handlers(registry: NativeViewRegistry) -> None:
248
+ """Register every built-in handler for the active platform."""
249
+ from ..utils import IS_ANDROID
250
+
251
+ if IS_ANDROID:
252
+ from .android import register_handlers
253
+ else:
254
+ from .ios import register_handlers
255
+ register_handlers(registry)
256
+
257
+
258
+ def _install_sdk_handlers(registry: NativeViewRegistry) -> None:
259
+ """Copy decorator-registered SDK handlers + entry-point plugins.
260
+
261
+ Imported lazily so unit tests that never touch the SDK don't pay the
262
+ entry-point discovery cost.
263
+ """
264
+ try:
265
+ from ..sdk._components import install_into_registry as _sdk_install
266
+ except Exception:
267
+ return
268
+ try:
269
+ _sdk_install(registry, _active_platform_name())
270
+ except Exception:
271
+ # A misbehaving plugin must not break PythonNative's startup.
272
+ pass
273
+
274
+
240
275
  def get_registry() -> NativeViewRegistry:
241
276
  """Return the process-wide registry, lazily registering handlers.
242
277
 
243
- The first call instantiates the registry and registers either the
244
- Android or iOS handlers based on `IS_ANDROID`. Subsequent calls
245
- return the same instance.
278
+ The first call instantiates the registry, registers either the
279
+ Android or iOS handlers based on `IS_ANDROID`, then layers on every
280
+ decorator-registered SDK handler (and any handlers exposed by
281
+ third-party packages via the
282
+ [`pythonnative.handlers`][pythonnative.sdk.ENTRY_POINT_GROUP] entry
283
+ point group). Subsequent calls return the same instance.
246
284
 
247
285
  Returns:
248
286
  The active `NativeViewRegistry`.
@@ -251,30 +289,40 @@ def get_registry() -> NativeViewRegistry:
251
289
  if _registry is not None:
252
290
  return _registry
253
291
  _registry = NativeViewRegistry()
292
+ _register_builtin_handlers(_registry)
293
+ _install_sdk_handlers(_registry)
294
+ return _registry
254
295
 
255
- from ..utils import IS_ANDROID
256
296
 
257
- if IS_ANDROID:
258
- from .android import register_handlers
297
+ def refresh_registry() -> NativeViewRegistry:
298
+ """Re-run SDK handler installation against the existing registry.
259
299
 
260
- register_handlers(_registry)
261
- else:
262
- from .ios import register_handlers
300
+ Call this after registering a new component at runtime if the
301
+ registry has already been instantiated. This is mostly useful in
302
+ REPL sessions and tests; the normal flow is "register, then call
303
+ [`get_registry`][pythonnative.native_views.get_registry]" and the
304
+ handlers come along automatically.
263
305
 
264
- register_handlers(_registry)
265
- return _registry
306
+ Returns:
307
+ The active `NativeViewRegistry`.
308
+ """
309
+ registry = get_registry()
310
+ _install_sdk_handlers(registry)
311
+ return registry
266
312
 
267
313
 
268
- def set_registry(registry: NativeViewRegistry) -> None:
314
+ def set_registry(registry: Optional[NativeViewRegistry]) -> None:
269
315
  """Install a custom registry (primarily for testing).
270
316
 
271
317
  Replaces the lazy singleton so subsequent
272
318
  [`get_registry`][pythonnative.native_views.get_registry] calls
273
319
  return `registry`. Pass a mock to drive the reconciler from
274
- unit tests without touching real native APIs.
320
+ unit tests without touching real native APIs. Pass ``None`` to
321
+ reset the singleton; the next ``get_registry`` call will then
322
+ rebuild it from scratch.
275
323
 
276
324
  Args:
277
- registry: The replacement registry.
325
+ registry: The replacement registry, or ``None`` to clear.
278
326
  """
279
327
  global _registry
280
328
  _registry = registry
@@ -0,0 +1,132 @@
1
+ """Public extension surface for PythonNative.
2
+
3
+ The ``pythonnative.sdk`` package collects the *stable* extension
4
+ contract that third-party packages rely on: the
5
+ [`ViewHandler`][pythonnative.sdk.ViewHandler] protocol, the
6
+ [`Style`][pythonnative.sdk.Style] type, the
7
+ [`@native_component`][pythonnative.sdk.native_component] registration
8
+ decorator, and an
9
+ [`element_factory`][pythonnative.sdk.element_factory] helper for
10
+ producing strongly-typed element constructors.
11
+
12
+ A custom native component is three things:
13
+
14
+ 1. A typed, frozen [`Props`][pythonnative.sdk.Props] dataclass listing
15
+ the public properties the component accepts.
16
+ 2. One or more
17
+ [`ViewHandler`][pythonnative.sdk.ViewHandler] subclasses (one per
18
+ target platform) implementing creation, update, and child management
19
+ for the underlying native widget.
20
+ 3. A registration call (the
21
+ [`@native_component`][pythonnative.sdk.native_component] decorator,
22
+ or
23
+ [`register_component`][pythonnative.sdk.register_component] for
24
+ imperative use) that binds the props type and handler into the
25
+ process-wide registry.
26
+
27
+ Once registered, the component appears alongside the built-ins: the
28
+ reconciler, layout engine, and Fast Refresh treat it identically.
29
+
30
+ PyPI packages can ship handlers without users importing them
31
+ explicitly by declaring an entry point in the
32
+ ``pythonnative.handlers`` group; PythonNative discovers and imports
33
+ those modules the first time the registry is asked for a handler.
34
+
35
+ Example:
36
+ ```python
37
+ from dataclasses import dataclass
38
+ import pythonnative as pn
39
+ from pythonnative.sdk import (
40
+ Props,
41
+ ViewHandler,
42
+ element_factory,
43
+ native_component,
44
+ )
45
+
46
+
47
+ @dataclass(frozen=True)
48
+ class BadgeProps(Props):
49
+ text: str = ""
50
+ color: str = "#FF3B30"
51
+ style: pn.StyleProp = None
52
+
53
+
54
+ @native_component("Badge", props=BadgeProps, platforms=("ios",))
55
+ class IOSBadgeHandler(ViewHandler):
56
+ def create(self, props):
57
+ ...
58
+
59
+ def update(self, view, changed):
60
+ ...
61
+
62
+
63
+ Badge = element_factory("Badge")
64
+
65
+ @pn.component
66
+ def App():
67
+ return pn.Column(
68
+ Badge(text="3", color="#0A84FF"),
69
+ pn.Text("Inbox"),
70
+ )
71
+ ```
72
+ """
73
+
74
+ from ..element import Element
75
+ from ..native_views.base import ViewHandler, parse_color_int, resolve_padding
76
+ from ..style import (
77
+ Color,
78
+ Dimension,
79
+ EdgeInsets,
80
+ EdgeValue,
81
+ FlexDirection,
82
+ JustifyContent,
83
+ Overflow,
84
+ Position,
85
+ Style,
86
+ StyleProp,
87
+ TransformSpec,
88
+ style,
89
+ )
90
+ from ._components import (
91
+ ENTRY_POINT_GROUP,
92
+ Props,
93
+ element_factory,
94
+ get_props_type,
95
+ install_into_registry,
96
+ list_components,
97
+ native_component,
98
+ register_component,
99
+ unregister_component,
100
+ )
101
+
102
+ __all__ = [
103
+ # Core types
104
+ "Element",
105
+ "ViewHandler",
106
+ # Style types
107
+ "Color",
108
+ "Dimension",
109
+ "EdgeInsets",
110
+ "EdgeValue",
111
+ "FlexDirection",
112
+ "JustifyContent",
113
+ "Overflow",
114
+ "Position",
115
+ "Style",
116
+ "StyleProp",
117
+ "TransformSpec",
118
+ "style",
119
+ # SDK helpers (re-exported so users only import from one place)
120
+ "parse_color_int",
121
+ "resolve_padding",
122
+ # Native-component SDK
123
+ "ENTRY_POINT_GROUP",
124
+ "Props",
125
+ "element_factory",
126
+ "get_props_type",
127
+ "install_into_registry",
128
+ "list_components",
129
+ "native_component",
130
+ "register_component",
131
+ "unregister_component",
132
+ ]