pythonx-compose 0.1.0a2__tar.gz → 0.1.0a3__tar.gz
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.
- {pythonx_compose-0.1.0a2 → pythonx_compose-0.1.0a3}/PKG-INFO +11 -9
- {pythonx_compose-0.1.0a2 → pythonx_compose-0.1.0a3}/README.md +10 -8
- {pythonx_compose-0.1.0a2 → pythonx_compose-0.1.0a3}/pyproject.toml +1 -1
- pythonx_compose-0.1.0a3/pythonx/compose/__init__.pyi +1 -0
- {pythonx_compose-0.1.0a2 → pythonx_compose-0.1.0a3}/pythonx/compose/_reexport.py +110 -51
- {pythonx_compose-0.1.0a2 → pythonx_compose-0.1.0a3}/pythonx/compose/foundation/__init__.pyi +1 -1
- pythonx_compose-0.1.0a3/pythonx/compose/foundation/layout/__init__.pyi +2 -0
- {pythonx_compose-0.1.0a2 → pythonx_compose-0.1.0a3}/pythonx/compose/foundation/text/input/__init__.pyi +7 -1
- {pythonx_compose-0.1.0a2 → pythonx_compose-0.1.0a3}/pythonx/compose/layout/__init__.pyi +338 -136
- pythonx_compose-0.1.0a3/pythonx/compose/material/__init__.pyi +1 -0
- {pythonx_compose-0.1.0a2 → pythonx_compose-0.1.0a3}/pythonx/compose/material/icons/__init__.pyi +1 -1
- pythonx_compose-0.1.0a3/pythonx/compose/material3/__init__.py +330 -0
- pythonx_compose-0.1.0a3/pythonx/compose/material3/__init__.pyi +7033 -0
- {pythonx_compose-0.1.0a2 → pythonx_compose-0.1.0a3}/pythonx/compose/runtime/__init__.py +107 -18
- {pythonx_compose-0.1.0a2 → pythonx_compose-0.1.0a3}/pythonx/compose/runtime/__init__.pyi +19 -8
- {pythonx_compose-0.1.0a2 → pythonx_compose-0.1.0a3}/pythonx/compose/ui/__init__.pyi +88 -28
- pythonx_compose-0.1.0a3/pythonx/compose/ui/graphics/__init__.pyi +1544 -0
- {pythonx_compose-0.1.0a2 → pythonx_compose-0.1.0a3}/pythonx/compose/ui/graphics/vector/__init__.pyi +97 -1
- {pythonx_compose-0.1.0a2 → pythonx_compose-0.1.0a3}/pythonx/compose/ui/modifier.py +44 -28
- {pythonx_compose-0.1.0a2 → pythonx_compose-0.1.0a3}/pythonx/compose/ui/text/__init__.pyi +288 -11
- {pythonx_compose-0.1.0a2 → pythonx_compose-0.1.0a3}/pythonx/compose/ui/unit/__init__.pyi +100 -18
- {pythonx_compose-0.1.0a2 → pythonx_compose-0.1.0a3}/pythonx_compose.egg-info/PKG-INFO +11 -9
- {pythonx_compose-0.1.0a2 → pythonx_compose-0.1.0a3}/pythonx_compose.egg-info/SOURCES.txt +2 -0
- {pythonx_compose-0.1.0a2 → pythonx_compose-0.1.0a3}/tests/test_chain.py +62 -6
- pythonx_compose-0.1.0a3/tests/test_coroutine_scopes.py +104 -0
- pythonx_compose-0.1.0a3/tests/test_guide.py +59 -0
- {pythonx_compose-0.1.0a2 → pythonx_compose-0.1.0a3}/tests/test_legacy_modules.py +32 -0
- {pythonx_compose-0.1.0a2 → pythonx_compose-0.1.0a3}/tests/test_material3_module.py +94 -0
- {pythonx_compose-0.1.0a2 → pythonx_compose-0.1.0a3}/tests/test_modifier_module.py +22 -0
- {pythonx_compose-0.1.0a2 → pythonx_compose-0.1.0a3}/tests/test_runtime_module.py +1 -1
- {pythonx_compose-0.1.0a2 → pythonx_compose-0.1.0a3}/tests/test_stubs.py +42 -8
- pythonx_compose-0.1.0a3/tests/test_typedpython.py +108 -0
- {pythonx_compose-0.1.0a2 → pythonx_compose-0.1.0a3}/tests/test_typing.py +114 -1
- {pythonx_compose-0.1.0a2 → pythonx_compose-0.1.0a3}/tests/test_wheel.py +6 -2
- pythonx_compose-0.1.0a2/pythonx/compose/__init__.pyi +0 -1
- pythonx_compose-0.1.0a2/pythonx/compose/foundation/layout/__init__.pyi +0 -2
- pythonx_compose-0.1.0a2/pythonx/compose/material/__init__.pyi +0 -1
- pythonx_compose-0.1.0a2/pythonx/compose/material3/__init__.py +0 -24
- pythonx_compose-0.1.0a2/pythonx/compose/material3/__init__.pyi +0 -3308
- pythonx_compose-0.1.0a2/pythonx/compose/ui/graphics/__init__.pyi +0 -818
- pythonx_compose-0.1.0a2/tests/test_guide.py +0 -25
- {pythonx_compose-0.1.0a2 → pythonx_compose-0.1.0a3}/LICENSE +0 -0
- {pythonx_compose-0.1.0a2 → pythonx_compose-0.1.0a3}/pythonx/compose/__init__.py +0 -0
- {pythonx_compose-0.1.0a2 → pythonx_compose-0.1.0a3}/pythonx/compose/foundation/__init__.py +0 -0
- {pythonx_compose-0.1.0a2 → pythonx_compose-0.1.0a3}/pythonx/compose/foundation/layout/__init__.py +0 -0
- {pythonx_compose-0.1.0a2 → pythonx_compose-0.1.0a3}/pythonx/compose/foundation/text/__init__.py +0 -0
- {pythonx_compose-0.1.0a2 → pythonx_compose-0.1.0a3}/pythonx/compose/foundation/text/input/__init__.py +0 -0
- {pythonx_compose-0.1.0a2 → pythonx_compose-0.1.0a3}/pythonx/compose/layout/__init__.py +0 -0
- {pythonx_compose-0.1.0a2 → pythonx_compose-0.1.0a3}/pythonx/compose/layout/arrangement.py +0 -0
- {pythonx_compose-0.1.0a2 → pythonx_compose-0.1.0a3}/pythonx/compose/material/__init__.py +0 -0
- {pythonx_compose-0.1.0a2 → pythonx_compose-0.1.0a3}/pythonx/compose/material/icons/__init__.py +0 -0
- {pythonx_compose-0.1.0a2 → pythonx_compose-0.1.0a3}/pythonx/compose/py.typed +0 -0
- {pythonx_compose-0.1.0a2 → pythonx_compose-0.1.0a3}/pythonx/compose/pythonx-map.toml +0 -0
- {pythonx_compose-0.1.0a2 → pythonx_compose-0.1.0a3}/pythonx/compose/ui/__init__.py +0 -0
- {pythonx_compose-0.1.0a2 → pythonx_compose-0.1.0a3}/pythonx/compose/ui/alignment.py +0 -0
- {pythonx_compose-0.1.0a2 → pythonx_compose-0.1.0a3}/pythonx/compose/ui/graphics/__init__.py +0 -0
- {pythonx_compose-0.1.0a2 → pythonx_compose-0.1.0a3}/pythonx/compose/ui/graphics/vector/__init__.py +0 -0
- {pythonx_compose-0.1.0a2 → pythonx_compose-0.1.0a3}/pythonx/compose/ui/text/__init__.py +0 -0
- {pythonx_compose-0.1.0a2 → pythonx_compose-0.1.0a3}/pythonx/compose/ui/unit/__init__.py +0 -0
- {pythonx_compose-0.1.0a2 → pythonx_compose-0.1.0a3}/pythonx_compose.egg-info/dependency_links.txt +0 -0
- {pythonx_compose-0.1.0a2 → pythonx_compose-0.1.0a3}/pythonx_compose.egg-info/top_level.txt +0 -0
- {pythonx_compose-0.1.0a2 → pythonx_compose-0.1.0a3}/setup.cfg +0 -0
- {pythonx_compose-0.1.0a2 → pythonx_compose-0.1.0a3}/tests/test_alignment_modules.py +0 -0
- {pythonx_compose-0.1.0a2 → pythonx_compose-0.1.0a3}/tests/test_app_root.py +0 -0
- {pythonx_compose-0.1.0a2 → pythonx_compose-0.1.0a3}/tests/test_pythonx_map.py +0 -0
- {pythonx_compose-0.1.0a2 → pythonx_compose-0.1.0a3}/tests/test_release_version.py +0 -0
- {pythonx_compose-0.1.0a2 → pythonx_compose-0.1.0a3}/tests/test_remember_saveable.py +0 -0
- {pythonx_compose-0.1.0a2 → pythonx_compose-0.1.0a3}/tests/test_text_field.py +0 -0
- {pythonx_compose-0.1.0a2 → pythonx_compose-0.1.0a3}/tests/test_ui_init_module.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: pythonx-compose
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.0a3
|
|
4
4
|
Summary: Build Compose Multiplatform UIs in Python: Python Declarative UI Framework
|
|
5
5
|
Author: BREW (b-re-w), Jong-uk Lee (rnoro5122)
|
|
6
6
|
License-Expression: Apache-2.0
|
|
@@ -36,7 +36,7 @@ English | [한국어](https://github.com/thisisthepy/pythonx-compose/blob/main/d
|
|
|
36
36
|
[](https://github.com/thisisthepy/pythonx-compose/blob/main/pyproject.toml)
|
|
37
37
|
[](#-status)
|
|
38
38
|
|
|
39
|
-
[Guide](https://thisisthepy.github.io/pythonx-compose/) · [Getting started](https://thisisthepy.github.io/pythonx-compose/getting-started.html) · [Concepts](https://thisisthepy.github.io/pythonx-compose/concepts.html) · [Status](https://thisisthepy.github.io/pythonx-compose/status.html)
|
|
39
|
+
[Guide](https://thisisthepy.github.io/pythonx-compose/) · [Getting started](https://thisisthepy.github.io/pythonx-compose/getting-started.html) · [Concepts](https://thisisthepy.github.io/pythonx-compose/concepts.html) · [Runtimes](https://thisisthepy.github.io/pythonx-compose/guide-runtimes.html) · [Status](https://thisisthepy.github.io/pythonx-compose/status.html)
|
|
40
40
|
|
|
41
41
|
</div>
|
|
42
42
|
|
|
@@ -176,9 +176,10 @@ from a sibling [python-multiplatform](https://github.com/thisisthepy/python-mult
|
|
|
176
176
|
|
|
177
177
|
Distributed as the pip package **`pythonx-compose`** (Python 3.11 or later), providing the import
|
|
178
178
|
package `pythonx.compose`, with the manifest inside the wheel, and typed: `.pyi` stubs generated from
|
|
179
|
-
real Compose 1.11.1
|
|
180
|
-
|
|
181
|
-
|
|
179
|
+
real Compose 1.11.1-ext.2 (`org.thisisthepy.compose.*:1.11.1-ext.2` libraries, Gradle plugin
|
|
180
|
+
`org.thisisthepy.compose:1.11.1-ext.3` on Maven Central) and `py.typed` ship with it. It runs inside
|
|
181
|
+
an app that embeds CPython through [python-multiplatform](https://github.com/thisisthepy/python-multiplatform);
|
|
182
|
+
it is not a standalone desktop toolkit.
|
|
182
183
|
|
|
183
184
|
## 🧪 Status
|
|
184
185
|
|
|
@@ -190,17 +191,18 @@ standalone desktop toolkit.
|
|
|
190
191
|
| `Modifier` chains with snake_case methods, overload dispatch, `Dp` as a number | ✅ implemented and tested against the binder's layer |
|
|
191
192
|
| `Column`, `Row`, `Spacer` from `material3` as well as `layout` | ✅ implemented and tested |
|
|
192
193
|
| Method keyword arguments in `snake_case` | ✅ implemented and tested: module functions and methods (methods need python-multiplatform's `describe_member`) |
|
|
193
|
-
| The empty `Modifier` |
|
|
194
|
+
| The empty `Modifier` | ✅ implemented and tested: `Modifier.padding(16)` and `Card(modifier=Modifier)` from the class evaluate `Modifier.Companion` through `register_empty(Modifier, lambda: Modifier.Companion)` (#134, #138) |
|
|
194
195
|
| `Alignment` / `Arrangement` | ✅ implemented and tested: `Alignment.Center`, `Arrangement.spaced_by(8)`, and grouped `Alignment.Horizontal.End` beside `Alignment.End` |
|
|
195
196
|
| Material 3 widgets (`Text`, `Button`, `Card`, `TextField`, …) | 🟡 partial: re-exported by rule; render proofs per widget pending (#9) |
|
|
196
|
-
| Type stubs (`.pyi`) from real Compose 1.11.1, `py.typed` | ✅ implemented and checked with mypy; many types are still `Any` (#12) |
|
|
197
|
+
| Type stubs (`.pyi`) from real Compose 1.11.1-ext.2, `py.typed` | ✅ implemented and checked with mypy; many types are still `Any` (#12) |
|
|
197
198
|
| Distribution (`pythonx-compose`) | 🟡 partial: the wheel carries the manifest, the re-export rule, the stubs and `py.typed` |
|
|
198
199
|
| Declared app root (`@app`), no update call; state through Kotlin's `mutable_state_of` | 🟡 partial: binder path tested against a fake host shaped after python-multiplatform #38; against real Compose, python-multiplatform's notebook E2E passes (#11, #19); numbers and strings round-trip |
|
|
199
200
|
| `TextField(state=...)` with `TextFieldState` (`pythonx.compose.foundation.text.input`) | 🟡 partial: tested against a fake host shaped after python-multiplatform #73; the input-method (IME) composing proof is python-multiplatform E2E #26 (#10) |
|
|
200
201
|
| `DefaultIcons` (`Icons.Default`), written `DefaultIcons.Add` | 🟡 partial: tested against a fake host shaped after python-multiplatform #37/#38; `Icon(DefaultIcons.Add, …)` is drawn in python-multiplatform's render test |
|
|
201
|
-
| Colour schemes |
|
|
202
|
+
| Colour schemes (`light_color_scheme`, `dark_color_scheme`) | ✅ implemented and tested: 48 Material 3 token defaults and `surface_tint = primary` through `ColorScheme`'s 48-argument constructor (#135) |
|
|
202
203
|
| `remember_saveable` with the notebook's `getValue()` / `setValue()` | 🟡 partial: tested against a fake host; save and restore is python-multiplatform's test (#174) |
|
|
203
|
-
| Coroutine scopes (`DefaultCoroutineScope`, `MainCoroutineScope`) |
|
|
204
|
+
| Coroutine scopes (`DefaultCoroutineScope`, `MainCoroutineScope`) | ✅ implemented and tested on top of `pythonx.concurrent` (#94) |
|
|
205
|
+
| Desktop runtime paths ([Runtimes](https://thisisthepy.github.io/pythonx-compose/guide-runtimes.html), #110) | 🟡 partial: zero JVM/AWT tokens in `pythonx/`; verified across all 20 `UI.ipynb` scenarios on default Compose JVM and extended GraalVM native-image (no `libjvm`); Kotlin/Native desktop (`macosArm64`, `linuxX64`) supported in `:python-multiplatform` (#370), pending `:python-multiplatform-compose` K/N targets |
|
|
204
206
|
|
|
205
207
|
The full list is on the guide's [Status page](https://thisisthepy.github.io/pythonx-compose/status.html).
|
|
206
208
|
|
|
@@ -11,7 +11,7 @@ English | [한국어](https://github.com/thisisthepy/pythonx-compose/blob/main/d
|
|
|
11
11
|
[](https://github.com/thisisthepy/pythonx-compose/blob/main/pyproject.toml)
|
|
12
12
|
[](#-status)
|
|
13
13
|
|
|
14
|
-
[Guide](https://thisisthepy.github.io/pythonx-compose/) · [Getting started](https://thisisthepy.github.io/pythonx-compose/getting-started.html) · [Concepts](https://thisisthepy.github.io/pythonx-compose/concepts.html) · [Status](https://thisisthepy.github.io/pythonx-compose/status.html)
|
|
14
|
+
[Guide](https://thisisthepy.github.io/pythonx-compose/) · [Getting started](https://thisisthepy.github.io/pythonx-compose/getting-started.html) · [Concepts](https://thisisthepy.github.io/pythonx-compose/concepts.html) · [Runtimes](https://thisisthepy.github.io/pythonx-compose/guide-runtimes.html) · [Status](https://thisisthepy.github.io/pythonx-compose/status.html)
|
|
15
15
|
|
|
16
16
|
</div>
|
|
17
17
|
|
|
@@ -151,9 +151,10 @@ from a sibling [python-multiplatform](https://github.com/thisisthepy/python-mult
|
|
|
151
151
|
|
|
152
152
|
Distributed as the pip package **`pythonx-compose`** (Python 3.11 or later), providing the import
|
|
153
153
|
package `pythonx.compose`, with the manifest inside the wheel, and typed: `.pyi` stubs generated from
|
|
154
|
-
real Compose 1.11.1
|
|
155
|
-
|
|
156
|
-
|
|
154
|
+
real Compose 1.11.1-ext.2 (`org.thisisthepy.compose.*:1.11.1-ext.2` libraries, Gradle plugin
|
|
155
|
+
`org.thisisthepy.compose:1.11.1-ext.3` on Maven Central) and `py.typed` ship with it. It runs inside
|
|
156
|
+
an app that embeds CPython through [python-multiplatform](https://github.com/thisisthepy/python-multiplatform);
|
|
157
|
+
it is not a standalone desktop toolkit.
|
|
157
158
|
|
|
158
159
|
## 🧪 Status
|
|
159
160
|
|
|
@@ -165,17 +166,18 @@ standalone desktop toolkit.
|
|
|
165
166
|
| `Modifier` chains with snake_case methods, overload dispatch, `Dp` as a number | ✅ implemented and tested against the binder's layer |
|
|
166
167
|
| `Column`, `Row`, `Spacer` from `material3` as well as `layout` | ✅ implemented and tested |
|
|
167
168
|
| Method keyword arguments in `snake_case` | ✅ implemented and tested: module functions and methods (methods need python-multiplatform's `describe_member`) |
|
|
168
|
-
| The empty `Modifier` |
|
|
169
|
+
| The empty `Modifier` | ✅ implemented and tested: `Modifier.padding(16)` and `Card(modifier=Modifier)` from the class evaluate `Modifier.Companion` through `register_empty(Modifier, lambda: Modifier.Companion)` (#134, #138) |
|
|
169
170
|
| `Alignment` / `Arrangement` | ✅ implemented and tested: `Alignment.Center`, `Arrangement.spaced_by(8)`, and grouped `Alignment.Horizontal.End` beside `Alignment.End` |
|
|
170
171
|
| Material 3 widgets (`Text`, `Button`, `Card`, `TextField`, …) | 🟡 partial: re-exported by rule; render proofs per widget pending (#9) |
|
|
171
|
-
| Type stubs (`.pyi`) from real Compose 1.11.1, `py.typed` | ✅ implemented and checked with mypy; many types are still `Any` (#12) |
|
|
172
|
+
| Type stubs (`.pyi`) from real Compose 1.11.1-ext.2, `py.typed` | ✅ implemented and checked with mypy; many types are still `Any` (#12) |
|
|
172
173
|
| Distribution (`pythonx-compose`) | 🟡 partial: the wheel carries the manifest, the re-export rule, the stubs and `py.typed` |
|
|
173
174
|
| Declared app root (`@app`), no update call; state through Kotlin's `mutable_state_of` | 🟡 partial: binder path tested against a fake host shaped after python-multiplatform #38; against real Compose, python-multiplatform's notebook E2E passes (#11, #19); numbers and strings round-trip |
|
|
174
175
|
| `TextField(state=...)` with `TextFieldState` (`pythonx.compose.foundation.text.input`) | 🟡 partial: tested against a fake host shaped after python-multiplatform #73; the input-method (IME) composing proof is python-multiplatform E2E #26 (#10) |
|
|
175
176
|
| `DefaultIcons` (`Icons.Default`), written `DefaultIcons.Add` | 🟡 partial: tested against a fake host shaped after python-multiplatform #37/#38; `Icon(DefaultIcons.Add, …)` is drawn in python-multiplatform's render test |
|
|
176
|
-
| Colour schemes |
|
|
177
|
+
| Colour schemes (`light_color_scheme`, `dark_color_scheme`) | ✅ implemented and tested: 48 Material 3 token defaults and `surface_tint = primary` through `ColorScheme`'s 48-argument constructor (#135) |
|
|
177
178
|
| `remember_saveable` with the notebook's `getValue()` / `setValue()` | 🟡 partial: tested against a fake host; save and restore is python-multiplatform's test (#174) |
|
|
178
|
-
| Coroutine scopes (`DefaultCoroutineScope`, `MainCoroutineScope`) |
|
|
179
|
+
| Coroutine scopes (`DefaultCoroutineScope`, `MainCoroutineScope`) | ✅ implemented and tested on top of `pythonx.concurrent` (#94) |
|
|
180
|
+
| Desktop runtime paths ([Runtimes](https://thisisthepy.github.io/pythonx-compose/guide-runtimes.html), #110) | 🟡 partial: zero JVM/AWT tokens in `pythonx/`; verified across all 20 `UI.ipynb` scenarios on default Compose JVM and extended GraalVM native-image (no `libjvm`); Kotlin/Native desktop (`macosArm64`, `linuxX64`) supported in `:python-multiplatform` (#370), pending `:python-multiplatform-compose` K/N targets |
|
|
179
181
|
|
|
180
182
|
The full list is on the guide's [Status page](https://thisisthepy.github.io/pythonx-compose/status.html).
|
|
181
183
|
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "pythonx-compose"
|
|
7
|
-
version = "0.1.
|
|
7
|
+
version = "0.1.0a3"
|
|
8
8
|
description = "Build Compose Multiplatform UIs in Python: Python Declarative UI Framework"
|
|
9
9
|
readme = {file = "README.md", content-type = "text/markdown"}
|
|
10
10
|
license = "Apache-2.0"
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
# GENERATED by scripts/gen_stubs.py from python-multiplatform's Kotlin-named stubs (artifact ks, workflow run 42402779166, commit 9507e6dd, Compose 1.11.1-ext.2). Do not edit.
|
|
@@ -50,8 +50,10 @@ import re
|
|
|
50
50
|
import sys
|
|
51
51
|
import types
|
|
52
52
|
import weakref
|
|
53
|
+
from collections.abc import Callable, Iterable, Mapping
|
|
53
54
|
from functools import lru_cache
|
|
54
55
|
from pathlib import Path
|
|
56
|
+
from typing import Protocol, TypedDict, cast
|
|
55
57
|
|
|
56
58
|
ROOT_MODULE = "python_multiplatform"
|
|
57
59
|
BINDING_MODULE = "python_multiplatform.binding"
|
|
@@ -61,6 +63,30 @@ _MANIFEST = Path(__file__).resolve().parent / "pythonx-map.toml"
|
|
|
61
63
|
_LOWER_UPPER = re.compile(r"([a-z0-9])([A-Z])")
|
|
62
64
|
_ACRONYM_WORD = re.compile(r"([A-Z]+)([A-Z][a-z])")
|
|
63
65
|
|
|
66
|
+
# `pythonx-map.toml`'s shape. Its tables are keyed by Kotlin and module names, so the hyphenated
|
|
67
|
+
# keys are spelled in the functional form.
|
|
68
|
+
_ValueClasses = TypedDict("_ValueClasses", {"raw-primitive-allowed": list[str]}, total=False)
|
|
69
|
+
Manifest = TypedDict(
|
|
70
|
+
"Manifest",
|
|
71
|
+
{
|
|
72
|
+
"modules": dict[str, str],
|
|
73
|
+
"value-classes": _ValueClasses,
|
|
74
|
+
"aliases": dict[str, dict[str, "list[str] | dict[str, str]"]],
|
|
75
|
+
},
|
|
76
|
+
total=False,
|
|
77
|
+
)
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
class BindingLayer(Protocol):
|
|
81
|
+
"""What this module asks of `python_multiplatform.binding`."""
|
|
82
|
+
|
|
83
|
+
def allow_raw_primitive(self, kotlin_type: str, /) -> None: ...
|
|
84
|
+
def register_empty(self, kotlin_type: object, empty_factory: object, /) -> None: ...
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
Describe = Callable[[types.ModuleType, str], object]
|
|
88
|
+
"""`python_multiplatform.describe(module, name)`."""
|
|
89
|
+
|
|
64
90
|
|
|
65
91
|
def snake_case(kotlin_name: str) -> str:
|
|
66
92
|
"""`fillMaxWidth` -> `fill_max_width`, `toURLString` -> `to_url_string`, `zIndex` -> `z_index`."""
|
|
@@ -80,12 +106,12 @@ def python_name(kotlin_name: str) -> str:
|
|
|
80
106
|
|
|
81
107
|
|
|
82
108
|
@lru_cache(maxsize=1)
|
|
83
|
-
def manifest() ->
|
|
109
|
+
def manifest() -> Manifest:
|
|
84
110
|
"""`pythonx-map.toml`, parsed once."""
|
|
85
111
|
import tomllib
|
|
86
112
|
|
|
87
113
|
with _MANIFEST.open("rb") as handle:
|
|
88
|
-
return tomllib.load(handle)
|
|
114
|
+
return cast(Manifest, tomllib.load(handle))
|
|
89
115
|
|
|
90
116
|
|
|
91
117
|
def kotlin_package_of(module_name: str) -> str:
|
|
@@ -97,7 +123,7 @@ def kotlin_package_of(module_name: str) -> str:
|
|
|
97
123
|
) from None
|
|
98
124
|
|
|
99
125
|
|
|
100
|
-
def binding_layer():
|
|
126
|
+
def binding_layer() -> BindingLayer:
|
|
101
127
|
"""The binder's installed binding module, or a `RuntimeError` that says what did not happen."""
|
|
102
128
|
binding = sys.modules.get(BINDING_MODULE)
|
|
103
129
|
if binding is None:
|
|
@@ -105,34 +131,47 @@ def binding_layer():
|
|
|
105
131
|
"the python-multiplatform binding layer is not installed: the Kotlin host must run "
|
|
106
132
|
"PythonxAdapter.install() before pythonx.compose can resolve a Kotlin declaration"
|
|
107
133
|
)
|
|
108
|
-
return binding
|
|
134
|
+
return cast(BindingLayer, binding)
|
|
109
135
|
|
|
110
136
|
|
|
111
|
-
_PRIMED = weakref.WeakSet()
|
|
137
|
+
_PRIMED: weakref.WeakSet[BindingLayer] = weakref.WeakSet()
|
|
138
|
+
_MODIFIER_TYPE = "androidx.compose.ui.Modifier"
|
|
112
139
|
|
|
113
140
|
|
|
114
|
-
def
|
|
115
|
-
"""
|
|
141
|
+
def _modifier_companion() -> object:
|
|
142
|
+
"""`Modifier.Companion` read live from `androidx.compose.ui.Modifier` (python-multiplatform #324)."""
|
|
143
|
+
return cast(object, importlib.import_module("androidx.compose.ui").Modifier.Companion)
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
def _prime(binding: BindingLayer) -> None:
|
|
147
|
+
"""Hand this binding layer the manifest's value-class allowlist and the default empty `Modifier`, once.
|
|
116
148
|
|
|
117
149
|
Method names and keywords on a proxy are the binder's own since python-multiplatform #131
|
|
118
|
-
(snake_case by this package's rule); nothing is registered for them here.
|
|
150
|
+
(snake_case by this package's rule); nothing is registered for them here. Since
|
|
151
|
+
python-multiplatform #324, `Modifier.Companion` is bound as a value on the proxy class and
|
|
152
|
+
`register_empty` accepts a callable (`SPEC.md` S6.2, #138); if the caller has not already
|
|
153
|
+
registered an explicit empty factory for `androidx.compose.ui.Modifier`, `_modifier_companion`
|
|
154
|
+
is registered as the default.
|
|
119
155
|
"""
|
|
120
156
|
if binding in _PRIMED:
|
|
121
157
|
return
|
|
122
158
|
for kotlin_type in manifest().get("value-classes", {}).get("raw-primitive-allowed", ()):
|
|
123
159
|
binding.allow_raw_primitive(kotlin_type)
|
|
160
|
+
factories = cast("Mapping[str, object]", getattr(binding, "_EMPTY_FACTORIES", {}))
|
|
161
|
+
if _MODIFIER_TYPE not in factories:
|
|
162
|
+
binding.register_empty(_MODIFIER_TYPE, _modifier_companion)
|
|
124
163
|
_PRIMED.add(binding)
|
|
125
164
|
|
|
126
165
|
|
|
127
|
-
def kotlin_module(module_name: str):
|
|
166
|
+
def kotlin_module(module_name: str) -> types.ModuleType:
|
|
128
167
|
"""The binder's module for the Kotlin package `module_name` maps to."""
|
|
129
168
|
_prime(binding_layer())
|
|
130
169
|
return importlib.import_module(kotlin_package_of(module_name))
|
|
131
170
|
|
|
132
171
|
|
|
133
|
-
def _name_table(kotlin) -> dict:
|
|
172
|
+
def _name_table(kotlin: types.ModuleType) -> dict[str, str]:
|
|
134
173
|
"""Pythonic name -> Kotlin name for everything the Kotlin module reports, with collisions refused."""
|
|
135
|
-
table = {}
|
|
174
|
+
table: dict[str, str] = {}
|
|
136
175
|
for kotlin_name in dir(kotlin):
|
|
137
176
|
if kotlin_name.startswith("_"):
|
|
138
177
|
continue
|
|
@@ -147,7 +186,7 @@ def _name_table(kotlin) -> dict:
|
|
|
147
186
|
return table
|
|
148
187
|
|
|
149
188
|
|
|
150
|
-
def _declared_type(kotlin, kotlin_name: str):
|
|
189
|
+
def _declared_type(kotlin: types.ModuleType, kotlin_name: str) -> tuple[object, str | None] | None:
|
|
151
190
|
"""`(kind, declared return type)` of the bound name [kotlin_name] in [kotlin], without reading it.
|
|
152
191
|
|
|
153
192
|
`python_multiplatform.describe(module, name)` answers from the binder's table and never runs a
|
|
@@ -155,7 +194,7 @@ def _declared_type(kotlin, kotlin_name: str):
|
|
|
155
194
|
that is not a single bound declaration (an overload set, a child package), answers `None`.
|
|
156
195
|
"""
|
|
157
196
|
root = sys.modules.get(ROOT_MODULE)
|
|
158
|
-
describe = getattr(root, "describe", None)
|
|
197
|
+
describe = cast("Describe | None", getattr(root, "describe", None))
|
|
159
198
|
if describe is None:
|
|
160
199
|
return None
|
|
161
200
|
try:
|
|
@@ -164,10 +203,13 @@ def _declared_type(kotlin, kotlin_name: str):
|
|
|
164
203
|
return None
|
|
165
204
|
if not isinstance(described, dict):
|
|
166
205
|
return None
|
|
167
|
-
|
|
206
|
+
# The binder's answer is a plain dict of strings; its values are only compared and sliced here.
|
|
207
|
+
answer = cast(dict[str, object], described)
|
|
208
|
+
returns = answer.get("returns")
|
|
209
|
+
return answer.get("kind"), returns if isinstance(returns, str) else None
|
|
168
210
|
|
|
169
211
|
|
|
170
|
-
def _pythonic(value
|
|
212
|
+
def _pythonic(value: object) -> object:
|
|
171
213
|
if isinstance(value, types.ModuleType):
|
|
172
214
|
# A Kotlin object served as a sub-package. Since python-multiplatform #35 the binder lists it
|
|
173
215
|
# and exposes it as an attribute of its parent, so the name table reaches it directly.
|
|
@@ -177,7 +219,7 @@ def _pythonic(value, name):
|
|
|
177
219
|
return value
|
|
178
220
|
|
|
179
221
|
|
|
180
|
-
def _resolve(kotlin, name: str):
|
|
222
|
+
def _resolve(kotlin: types.ModuleType, name: str) -> tuple[object, bool] | None:
|
|
181
223
|
"""What the Pythonic [name] means in the binder's module [kotlin]: `(value, cacheable)` or `None`.
|
|
182
224
|
|
|
183
225
|
A name the module lists is looked up by the rule. An upper-case name it does not list may be a
|
|
@@ -187,10 +229,10 @@ def _resolve(kotlin, name: str):
|
|
|
187
229
|
"""
|
|
188
230
|
kotlin_name = _name_table(kotlin).get(name)
|
|
189
231
|
if kotlin_name is not None:
|
|
190
|
-
value = getattr(kotlin, kotlin_name)
|
|
232
|
+
value = cast(object, getattr(kotlin, kotlin_name))
|
|
191
233
|
# The binder declined to cache it -- a live property, read again on every access -- so this
|
|
192
234
|
# layer does not freeze it either.
|
|
193
|
-
return _pythonic(value
|
|
235
|
+
return _pythonic(value), kotlin_name in cast(Mapping[str, object], vars(kotlin))
|
|
194
236
|
if name[:1].isupper():
|
|
195
237
|
try:
|
|
196
238
|
nested = importlib.import_module(kotlin.__name__ + "." + name)
|
|
@@ -203,7 +245,7 @@ def _resolve(kotlin, name: str):
|
|
|
203
245
|
_STATIC_GETTER = "STATIC_GETTER"
|
|
204
246
|
|
|
205
247
|
|
|
206
|
-
def _constant_groups(kotlin) -> dict:
|
|
248
|
+
def _constant_groups(kotlin: types.ModuleType) -> dict[str, dict[str, str]]:
|
|
207
249
|
"""Group name -> {Pythonic constant name: Kotlin name}, for the Kotlin object [kotlin].
|
|
208
250
|
|
|
209
251
|
A group is a type nested in the object, `P.G`, that some constant of `P` is declared as; it holds
|
|
@@ -213,12 +255,13 @@ def _constant_groups(kotlin) -> dict:
|
|
|
213
255
|
"""
|
|
214
256
|
prefix = kotlin.__name__ + "."
|
|
215
257
|
members = _name_table(kotlin)
|
|
216
|
-
groups: dict = {}
|
|
258
|
+
groups: dict[str, dict[str, str]] = {}
|
|
217
259
|
for pythonic, kotlin_name in members.items():
|
|
218
260
|
declared = _declared_type(kotlin, kotlin_name)
|
|
219
261
|
if declared is None or declared[0] != _STATIC_GETTER or not declared[1]:
|
|
220
262
|
continue
|
|
221
|
-
|
|
263
|
+
returns = declared[1]
|
|
264
|
+
group = returns[len(prefix):] if returns.startswith(prefix) else ""
|
|
222
265
|
if not group[:1].isupper() or "." in group or group in members:
|
|
223
266
|
continue
|
|
224
267
|
groups.setdefault(group, {})[pythonic] = kotlin_name
|
|
@@ -236,17 +279,24 @@ class KotlinObject:
|
|
|
236
279
|
|
|
237
280
|
__slots__ = ("_kotlin", "_cache", "_groups")
|
|
238
281
|
|
|
239
|
-
|
|
282
|
+
_kotlin: types.ModuleType
|
|
283
|
+
_cache: dict[str, object]
|
|
284
|
+
_groups: dict[str, dict[str, str]] | None
|
|
285
|
+
|
|
286
|
+
def __init__(self, kotlin_module: types.ModuleType) -> None:
|
|
240
287
|
object.__setattr__(self, "_kotlin", kotlin_module)
|
|
241
|
-
object.__setattr__(self, "_cache",
|
|
288
|
+
object.__setattr__(self, "_cache", dict[str, object]())
|
|
242
289
|
object.__setattr__(self, "_groups", None)
|
|
243
290
|
|
|
244
|
-
def _constant_groups(self) -> dict:
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
291
|
+
def _constant_groups(self) -> dict[str, dict[str, str]]:
|
|
292
|
+
known = self._groups
|
|
293
|
+
if known is not None:
|
|
294
|
+
return known
|
|
295
|
+
groups = _constant_groups(self._kotlin)
|
|
296
|
+
object.__setattr__(self, "_groups", groups)
|
|
297
|
+
return groups
|
|
248
298
|
|
|
249
|
-
def __getattr__(self, name):
|
|
299
|
+
def __getattr__(self, name: str) -> object:
|
|
250
300
|
if name.startswith("__") and name.endswith("__"):
|
|
251
301
|
raise AttributeError(name)
|
|
252
302
|
cached = self._cache.get(name)
|
|
@@ -260,19 +310,21 @@ class KotlinObject:
|
|
|
260
310
|
f"{self._kotlin.__name__} has nothing spelled {name!r} under pythonx's naming rule"
|
|
261
311
|
)
|
|
262
312
|
# The grouping is metadata and is kept; the constants inside are read on each access.
|
|
263
|
-
|
|
313
|
+
group = ConstantGroup(self, f"{self._kotlin.__name__}.{name}", members)
|
|
314
|
+
self._cache[name] = group
|
|
315
|
+
return group
|
|
264
316
|
value, cacheable = resolved
|
|
265
317
|
if cacheable:
|
|
266
318
|
self._cache[name] = value
|
|
267
319
|
return value
|
|
268
320
|
|
|
269
|
-
def __setattr__(self, name, value):
|
|
321
|
+
def __setattr__(self, name: str, value: object) -> None:
|
|
270
322
|
raise AttributeError(f"{self._kotlin.__name__} is a Kotlin object; its members are read-only here")
|
|
271
323
|
|
|
272
|
-
def __dir__(self):
|
|
324
|
+
def __dir__(self) -> list[str]:
|
|
273
325
|
return sorted(set(_name_table(self._kotlin)) | set(self._constant_groups()))
|
|
274
326
|
|
|
275
|
-
def __repr__(self):
|
|
327
|
+
def __repr__(self) -> str:
|
|
276
328
|
return f"<pythonx view of Kotlin object {self._kotlin.__name__}>"
|
|
277
329
|
|
|
278
330
|
|
|
@@ -286,11 +338,11 @@ class CallableKotlinObject(KotlinObject):
|
|
|
286
338
|
|
|
287
339
|
__slots__ = ()
|
|
288
340
|
|
|
289
|
-
def __call__(self, *args, **kwargs):
|
|
290
|
-
return self._kotlin(*args, **kwargs)
|
|
341
|
+
def __call__(self, *args: object, **kwargs: object) -> object:
|
|
342
|
+
return cast("Callable[..., object]", self._kotlin)(*args, **kwargs)
|
|
291
343
|
|
|
292
344
|
|
|
293
|
-
def _kotlin_object(kotlin_module):
|
|
345
|
+
def _kotlin_object(kotlin_module: types.ModuleType) -> KotlinObject:
|
|
294
346
|
"""The namespace for a Kotlin object module: callable only when the binder made the module callable."""
|
|
295
347
|
return CallableKotlinObject(kotlin_module) if callable(kotlin_module) else KotlinObject(kotlin_module)
|
|
296
348
|
|
|
@@ -305,12 +357,16 @@ class ConstantGroup:
|
|
|
305
357
|
|
|
306
358
|
__slots__ = ("_owner", "_kotlin_type", "_members")
|
|
307
359
|
|
|
308
|
-
|
|
360
|
+
_owner: KotlinObject
|
|
361
|
+
_kotlin_type: str
|
|
362
|
+
_members: dict[str, str]
|
|
363
|
+
|
|
364
|
+
def __init__(self, owner: KotlinObject, kotlin_type: str, members: Mapping[str, str]) -> None:
|
|
309
365
|
object.__setattr__(self, "_owner", owner)
|
|
310
366
|
object.__setattr__(self, "_kotlin_type", kotlin_type)
|
|
311
367
|
object.__setattr__(self, "_members", dict(members))
|
|
312
368
|
|
|
313
|
-
def __getattr__(self, name):
|
|
369
|
+
def __getattr__(self, name: str) -> object:
|
|
314
370
|
if name.startswith("__") and name.endswith("__"):
|
|
315
371
|
raise AttributeError(name)
|
|
316
372
|
if name not in self._members:
|
|
@@ -319,33 +375,36 @@ class ConstantGroup:
|
|
|
319
375
|
f"{self._owner._kotlin.__name__} declared as {self._kotlin_type}: "
|
|
320
376
|
+ ", ".join(sorted(self._members))
|
|
321
377
|
)
|
|
322
|
-
|
|
378
|
+
# The owner's own resolver, by name: a constant is never an attribute of `KotlinObject` itself.
|
|
379
|
+
return self._owner.__getattr__(name)
|
|
323
380
|
|
|
324
|
-
def __setattr__(self, name, value):
|
|
381
|
+
def __setattr__(self, name: str, value: object) -> None:
|
|
325
382
|
raise AttributeError(f"{self._kotlin_type} is a group of Kotlin constants; they are read-only here")
|
|
326
383
|
|
|
327
|
-
def __dir__(self):
|
|
384
|
+
def __dir__(self) -> list[str]:
|
|
328
385
|
return sorted(self._members)
|
|
329
386
|
|
|
330
|
-
def __repr__(self):
|
|
387
|
+
def __repr__(self) -> str:
|
|
331
388
|
return f"<pythonx group of {self._owner._kotlin.__name__} constants declared {self._kotlin_type}>"
|
|
332
389
|
|
|
333
390
|
|
|
334
|
-
def _aliases(module_name: str) -> dict:
|
|
391
|
+
def _aliases(module_name: str) -> dict[str, tuple[str, str]]:
|
|
335
392
|
"""Name -> `(source module, attribute path in it)`, per the manifest's `[aliases]`.
|
|
336
393
|
|
|
337
394
|
An entry under a source module is a list of names (lent under the same name, path = the name) or
|
|
338
395
|
a table `{ lent = "Path.To.Attr" }` (a different name, through an attribute path).
|
|
339
396
|
"""
|
|
340
|
-
borrowed = {}
|
|
397
|
+
borrowed: dict[str, tuple[str, str]] = {}
|
|
341
398
|
for source, lent in manifest().get("aliases", {}).get(module_name, {}).items():
|
|
342
|
-
pairs
|
|
399
|
+
pairs: Iterable[tuple[str, str]] = (
|
|
400
|
+
((name, name) for name in lent) if isinstance(lent, list) else lent.items()
|
|
401
|
+
)
|
|
343
402
|
for name, path in pairs:
|
|
344
403
|
borrowed[name] = (source, path)
|
|
345
404
|
return borrowed
|
|
346
405
|
|
|
347
406
|
|
|
348
|
-
def _borrow(module_name: str, name: str, source: str, path: str):
|
|
407
|
+
def _borrow(module_name: str, name: str, source: str, path: str) -> object:
|
|
349
408
|
"""The value of the alias [name]: [path] read in [source].
|
|
350
409
|
|
|
351
410
|
A bare name is resolved once and kept in the owner's namespace. A path with attribute steps is
|
|
@@ -353,22 +412,22 @@ def _borrow(module_name: str, name: str, source: str, path: str):
|
|
|
353
412
|
serves `Icons.Default` afresh on every read and this layer does not freeze it either.
|
|
354
413
|
"""
|
|
355
414
|
head, *rest = path.split(".")
|
|
356
|
-
value = getattr(importlib.import_module(source), head)
|
|
415
|
+
value = cast(object, getattr(importlib.import_module(source), head))
|
|
357
416
|
for step in rest:
|
|
358
|
-
value = getattr(value, step)
|
|
417
|
+
value = cast(object, getattr(value, step))
|
|
359
418
|
if not rest:
|
|
360
419
|
setattr(sys.modules[module_name], name, value)
|
|
361
420
|
return value
|
|
362
421
|
|
|
363
422
|
|
|
364
|
-
def reexport(module_name: str):
|
|
423
|
+
def reexport(module_name: str) -> tuple[Callable[[str], object], Callable[[], list[str]]]:
|
|
365
424
|
"""The module-level `__getattr__` and `__dir__` for the mapped package `module_name`.
|
|
366
425
|
|
|
367
426
|
Nothing is resolved here, only when a name is first read, so importing a `pythonx.compose`
|
|
368
427
|
module needs neither the manifest row's Kotlin package nor an installed binder.
|
|
369
428
|
"""
|
|
370
429
|
|
|
371
|
-
def __getattr__(name):
|
|
430
|
+
def __getattr__(name: str) -> object:
|
|
372
431
|
if name.startswith("__") and name.endswith("__"):
|
|
373
432
|
raise AttributeError(name)
|
|
374
433
|
if importlib.util.find_spec(module_name + "." + name) is not None:
|
|
@@ -396,8 +455,8 @@ def reexport(module_name: str):
|
|
|
396
455
|
setattr(sys.modules[module_name], name, value)
|
|
397
456
|
return value
|
|
398
457
|
|
|
399
|
-
def __dir__():
|
|
400
|
-
own = set(vars(sys.modules[module_name])) | set(_aliases(module_name))
|
|
458
|
+
def __dir__() -> list[str]:
|
|
459
|
+
own = set(cast(Mapping[str, object], vars(sys.modules[module_name]))) | set(_aliases(module_name))
|
|
401
460
|
try:
|
|
402
461
|
own.update(_name_table(kotlin_module(module_name)))
|
|
403
462
|
except (RuntimeError, ImportError):
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# GENERATED by scripts/gen_stubs.py from python-multiplatform's Kotlin-named stubs (artifact
|
|
1
|
+
# GENERATED by scripts/gen_stubs.py from python-multiplatform's Kotlin-named stubs (artifact ks, workflow run 42402779166, commit 9507e6dd, Compose 1.11.1-ext.2). Do not edit.
|
|
2
2
|
import typing as _t
|
|
3
3
|
|
|
4
4
|
class BorderStroke:
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# GENERATED by scripts/gen_stubs.py from python-multiplatform's Kotlin-named stubs (artifact
|
|
1
|
+
# GENERATED by scripts/gen_stubs.py from python-multiplatform's Kotlin-named stubs (artifact ks, workflow run 42402779166, commit 9507e6dd, Compose 1.11.1-ext.2). Do not edit.
|
|
2
2
|
import typing as _t
|
|
3
3
|
|
|
4
4
|
class _TextFieldState_clearText(_t.Protocol):
|
|
@@ -25,6 +25,9 @@ class OutputTransformation:
|
|
|
25
25
|
class TextFieldBuffer:
|
|
26
26
|
"""Kotlin: androidx.compose.foundation.text.input.TextFieldBuffer"""
|
|
27
27
|
|
|
28
|
+
class TextFieldDecorator:
|
|
29
|
+
"""Kotlin: androidx.compose.foundation.text.input.TextFieldDecorator"""
|
|
30
|
+
|
|
28
31
|
class TextFieldLineLimits:
|
|
29
32
|
"""Kotlin: androidx.compose.foundation.text.input.TextFieldLineLimits"""
|
|
30
33
|
|
|
@@ -37,6 +40,9 @@ class TextFieldState:
|
|
|
37
40
|
set_text_and_place_cursor_at_end: _t.ClassVar[_TextFieldState_setTextAndPlaceCursorAtEnd]
|
|
38
41
|
set_text_and_select_all: _t.ClassVar[_TextFieldState_setTextAndSelectAll]
|
|
39
42
|
to_text_field_buffer: _t.ClassVar[_TextFieldState_toTextFieldBuffer]
|
|
43
|
+
def edit(self, block: _t.Callable[[TextFieldBuffer], None]) -> None:
|
|
44
|
+
"""Kotlin member: androidx.compose.foundation.text.input.TextFieldState.edit"""
|
|
45
|
+
...
|
|
40
46
|
@property
|
|
41
47
|
def composition(self) -> _t.Any | None:
|
|
42
48
|
"""Kotlin property: androidx.compose.foundation.text.input.TextFieldState.composition (androidx.compose.foundation.text.input.TextFieldState.composition: androidx.compose.ui.text.TextRange?)"""
|