pythonx-compose 0.1.0a1__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.0a1/LICENSE +21 -0
- pythonx_compose-0.1.0a1/PKG-INFO +228 -0
- pythonx_compose-0.1.0a1/README.md +203 -0
- pythonx_compose-0.1.0a1/pyproject.toml +49 -0
- pythonx_compose-0.1.0a1/pythonx/compose/__init__.py +4 -0
- pythonx_compose-0.1.0a1/pythonx/compose/__init__.pyi +1 -0
- pythonx_compose-0.1.0a1/pythonx/compose/_reexport.py +512 -0
- pythonx_compose-0.1.0a1/pythonx/compose/foundation/__init__.py +5 -0
- pythonx_compose-0.1.0a1/pythonx/compose/foundation/__init__.pyi +17 -0
- pythonx_compose-0.1.0a1/pythonx/compose/foundation/layout/__init__.py +5 -0
- pythonx_compose-0.1.0a1/pythonx/compose/foundation/layout/__init__.pyi +2 -0
- pythonx_compose-0.1.0a1/pythonx/compose/foundation/text/__init__.py +5 -0
- pythonx_compose-0.1.0a1/pythonx/compose/foundation/text/input/__init__.py +5 -0
- pythonx_compose-0.1.0a1/pythonx/compose/foundation/text/input/__init__.pyi +78 -0
- pythonx_compose-0.1.0a1/pythonx/compose/layout/__init__.py +25 -0
- pythonx_compose-0.1.0a1/pythonx/compose/layout/__init__.pyi +878 -0
- pythonx_compose-0.1.0a1/pythonx/compose/layout/arrangement.py +34 -0
- pythonx_compose-0.1.0a1/pythonx/compose/material/__init__.py +8 -0
- pythonx_compose-0.1.0a1/pythonx/compose/material/__init__.pyi +1 -0
- pythonx_compose-0.1.0a1/pythonx/compose/material/icons/__init__.py +9 -0
- pythonx_compose-0.1.0a1/pythonx/compose/material/icons/__init__.pyi +1159 -0
- pythonx_compose-0.1.0a1/pythonx/compose/material3/__init__.py +24 -0
- pythonx_compose-0.1.0a1/pythonx/compose/material3/__init__.pyi +3305 -0
- pythonx_compose-0.1.0a1/pythonx/compose/py.typed +0 -0
- pythonx_compose-0.1.0a1/pythonx/compose/pythonx-map.toml +73 -0
- pythonx_compose-0.1.0a1/pythonx/compose/runtime/__init__.py +128 -0
- pythonx_compose-0.1.0a1/pythonx/compose/runtime/__init__.pyi +180 -0
- pythonx_compose-0.1.0a1/pythonx/compose/ui/__init__.py +21 -0
- pythonx_compose-0.1.0a1/pythonx/compose/ui/__init__.pyi +975 -0
- pythonx_compose-0.1.0a1/pythonx/compose/ui/alignment.py +47 -0
- pythonx_compose-0.1.0a1/pythonx/compose/ui/graphics/__init__.py +5 -0
- pythonx_compose-0.1.0a1/pythonx/compose/ui/graphics/__init__.pyi +795 -0
- pythonx_compose-0.1.0a1/pythonx/compose/ui/graphics/vector/__init__.py +5 -0
- pythonx_compose-0.1.0a1/pythonx/compose/ui/graphics/vector/__init__.pyi +221 -0
- pythonx_compose-0.1.0a1/pythonx/compose/ui/modifier.py +110 -0
- pythonx_compose-0.1.0a1/pythonx/compose/ui/text/__init__.py +5 -0
- pythonx_compose-0.1.0a1/pythonx/compose/ui/text/__init__.pyi +703 -0
- pythonx_compose-0.1.0a1/pythonx/compose/ui/unit/__init__.py +19 -0
- pythonx_compose-0.1.0a1/pythonx/compose/ui/unit/__init__.pyi +348 -0
- pythonx_compose-0.1.0a1/pythonx_compose.egg-info/PKG-INFO +228 -0
- pythonx_compose-0.1.0a1/pythonx_compose.egg-info/SOURCES.txt +57 -0
- pythonx_compose-0.1.0a1/pythonx_compose.egg-info/dependency_links.txt +1 -0
- pythonx_compose-0.1.0a1/pythonx_compose.egg-info/top_level.txt +1 -0
- pythonx_compose-0.1.0a1/setup.cfg +4 -0
- pythonx_compose-0.1.0a1/tests/test_alignment_modules.py +128 -0
- pythonx_compose-0.1.0a1/tests/test_app_root.py +287 -0
- pythonx_compose-0.1.0a1/tests/test_chain.py +768 -0
- pythonx_compose-0.1.0a1/tests/test_guide.py +25 -0
- pythonx_compose-0.1.0a1/tests/test_legacy_modules.py +111 -0
- pythonx_compose-0.1.0a1/tests/test_material3_module.py +244 -0
- pythonx_compose-0.1.0a1/tests/test_modifier_module.py +120 -0
- pythonx_compose-0.1.0a1/tests/test_pythonx_map.py +152 -0
- pythonx_compose-0.1.0a1/tests/test_release_version.py +47 -0
- pythonx_compose-0.1.0a1/tests/test_runtime_module.py +155 -0
- pythonx_compose-0.1.0a1/tests/test_stubs.py +647 -0
- pythonx_compose-0.1.0a1/tests/test_text_field.py +116 -0
- pythonx_compose-0.1.0a1/tests/test_typing.py +265 -0
- pythonx_compose-0.1.0a1/tests/test_ui_init_module.py +77 -0
- pythonx_compose-0.1.0a1/tests/test_wheel.py +91 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2023-2024 BREW (b-re-w), Jong-uk Lee (rnoro5122)
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: pythonx-compose
|
|
3
|
+
Version: 0.1.0a1
|
|
4
|
+
Summary: Pythonic Compose UI for Python on Kotlin Multiplatform, through python-multiplatform
|
|
5
|
+
Author: BREW (b-re-w), Jong-uk Lee (rnoro5122)
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/thisisthepy/pythonx-compose
|
|
8
|
+
Project-URL: Source, https://github.com/thisisthepy/pythonx-compose
|
|
9
|
+
Project-URL: Issues, https://github.com/thisisthepy/pythonx-compose/issues
|
|
10
|
+
Project-URL: Documentation, https://thisisthepy.github.io/pythonx-compose/
|
|
11
|
+
Keywords: compose,jetpack-compose,compose-multiplatform,kotlin-multiplatform,ui,python-multiplatform
|
|
12
|
+
Classifier: Development Status :: 3 - Alpha
|
|
13
|
+
Classifier: Intended Audience :: Developers
|
|
14
|
+
Classifier: Operating System :: OS Independent
|
|
15
|
+
Classifier: Programming Language :: Python :: 3
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
19
|
+
Classifier: Topic :: Software Development :: User Interfaces
|
|
20
|
+
Classifier: Typing :: Typed
|
|
21
|
+
Requires-Python: >=3.11
|
|
22
|
+
Description-Content-Type: text/markdown
|
|
23
|
+
License-File: LICENSE
|
|
24
|
+
Dynamic: license-file
|
|
25
|
+
|
|
26
|
+
English | [ํ๊ตญ์ด](https://github.com/thisisthepy/pythonx-compose/blob/main/docs/locale/README_ko.md)
|
|
27
|
+
|
|
28
|
+
<div align="center">
|
|
29
|
+
|
|
30
|
+
# pythonx-compose
|
|
31
|
+
|
|
32
|
+
**Write Compose user interfaces in Python โ with Compose's own widgets, spelled the Python way.**
|
|
33
|
+
|
|
34
|
+
[](https://github.com/thisisthepy/pythonx-compose/blob/main/LICENSE)
|
|
35
|
+
[](https://github.com/thisisthepy/pythonx-compose/blob/main/pyproject.toml)
|
|
36
|
+
[](https://github.com/thisisthepy/pythonx-compose/blob/main/pyproject.toml)
|
|
37
|
+
[](#-status)
|
|
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)
|
|
40
|
+
|
|
41
|
+
</div>
|
|
42
|
+
|
|
43
|
+
---
|
|
44
|
+
|
|
45
|
+
## Why
|
|
46
|
+
|
|
47
|
+
Jetpack Compose and Compose Multiplatform are a superb way to build UI โ if you write Kotlin.
|
|
48
|
+
`pythonx-compose` is for people who write Python. It does not reimplement Compose and it does not
|
|
49
|
+
invent a new widget set: it takes the real `androidx.compose.*` API, which
|
|
50
|
+
[python-multiplatform](https://github.com/thisisthepy/python-multiplatform) exposes to Python under
|
|
51
|
+
its Kotlin names, and restructures it into a Pythonic package, `pythonx.compose`.
|
|
52
|
+
|
|
53
|
+
```python
|
|
54
|
+
from pythonx.compose.runtime import Composable
|
|
55
|
+
from pythonx.compose.material3 import Text, Button
|
|
56
|
+
|
|
57
|
+
@Composable
|
|
58
|
+
def Greeting():
|
|
59
|
+
Button(on_click=lambda: print("hi"), content=lambda: Text("Hello from Python"))
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
<sub>These names resolve today, by one rule, inside an app that embeds the binder; per-widget render
|
|
63
|
+
proofs are still pending. See [Status](#-status).</sub>
|
|
64
|
+
|
|
65
|
+
## โจ Principles
|
|
66
|
+
|
|
67
|
+
- **The original API, Pythonic names.** Every parameter is Compose's own parameter, in
|
|
68
|
+
`snake_case`: `onClick` โ `on_click`, `horizontalAlignment` โ `horizontal_alignment`. Upper-case
|
|
69
|
+
names (types, objects, composables) keep their Kotlin spelling; every other name is `snake_case`
|
|
70
|
+
only (`fillMaxWidth` โ `fill_max_width`, `toURLString` โ `to_url_string`), and an explicit
|
|
71
|
+
overload keeps its suffix (`padding__Dp`).
|
|
72
|
+
- **Extension functions are methods.** `Modifier.padding(16).size(24).fill_max_width()` chains
|
|
73
|
+
exactly as it does in Kotlin. Keyword arguments to such a method are `snake_case`
|
|
74
|
+
too (`m.padding(padding_values=...)`), as they are on the module function
|
|
75
|
+
`padding(m, padding_values=...)`; Kotlin's own spelling still works at run time, and the type
|
|
76
|
+
stubs offer only the Pythonic one.
|
|
77
|
+
- **Kotlin objects are namespaces.** `Alignment.Center` and `Arrangement.End` are read without
|
|
78
|
+
parentheses; functions inside them are `snake_case`: `Arrangement.spaced_by(8)`. Constants can
|
|
79
|
+
also be reached grouped by their declared type, as the notebook writes them:
|
|
80
|
+
`Alignment.Horizontal.End` is `Alignment.End`.
|
|
81
|
+
- **`@Composable` stays.** Screens are decorated Python functions.
|
|
82
|
+
- **A real package.** `pythonx/` is ordinary Python source that imports `androidx.compose.*` and
|
|
83
|
+
reshapes it. The binder never renames anything โ renaming is this package's job.
|
|
84
|
+
- **One manifest.** [`pythonx-map.toml`](https://github.com/thisisthepy/pythonx-compose/blob/main/pythonx/compose/pythonx-map.toml) says which `pythonx.compose.*` module
|
|
85
|
+
stands for which Kotlin package. It lives inside the package and ships in the wheel. The runtime
|
|
86
|
+
and the `.pyi` generator read the same file, so what your editor completes is what the interpreter
|
|
87
|
+
resolves.
|
|
88
|
+
|
|
89
|
+
## ๐งฉ Architecture at a glance
|
|
90
|
+
|
|
91
|
+
```mermaid
|
|
92
|
+
flowchart LR
|
|
93
|
+
app["Your Python UI<br/>@Composable def Screen()"] --> px["pythonx.compose<br/>(this package, real Python)"]
|
|
94
|
+
px --> ax["androidx.compose.*<br/>Kotlin names, exposed by python-multiplatform"]
|
|
95
|
+
ax --> compose["Jetpack / Compose Multiplatform"]
|
|
96
|
+
map["pythonx-map.toml"] -.-> px
|
|
97
|
+
map -.-> pyi[".pyi stubs<br/>(in the wheel)"]
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
| Python module | Kotlin package |
|
|
101
|
+
|---|---|
|
|
102
|
+
| `pythonx.compose.runtime` | `androidx.compose.runtime` |
|
|
103
|
+
| `pythonx.compose.ui` | `androidx.compose.ui` |
|
|
104
|
+
| `pythonx.compose.layout` | `androidx.compose.foundation.layout` |
|
|
105
|
+
| `pythonx.compose.material3` | `androidx.compose.material3` |
|
|
106
|
+
|
|
107
|
+
Each module is a real file whose `__init__.py` calls one re-export rule; names resolve on first use.
|
|
108
|
+
`Column`, `Row` and `Spacer` are importable from `pythonx.compose.material3` as well as
|
|
109
|
+
`pythonx.compose.layout`, as the notebook writes them (the manifest's `[aliases]`).
|
|
110
|
+
|
|
111
|
+
## ๐ Quick start
|
|
112
|
+
|
|
113
|
+
> [!NOTE]
|
|
114
|
+
> `pythonx-compose` is **pre-alpha**. The first alpha, `0.1.0a1`, is released to PyPI when the
|
|
115
|
+
> maintainer tags it; a pre-release needs `pip install --pre pythonx-compose`.
|
|
116
|
+
|
|
117
|
+
From a clone, to run the tests:
|
|
118
|
+
|
|
119
|
+
```bash
|
|
120
|
+
git clone https://github.com/thisisthepy/pythonx-compose
|
|
121
|
+
cd pythonx-compose
|
|
122
|
+
python3 -m pip install pytest # inside a virtual environment
|
|
123
|
+
python3 -m pytest tests -q
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
What runs today, from the repository root:
|
|
127
|
+
|
|
128
|
+
```python
|
|
129
|
+
from pythonx.compose.runtime import Composable
|
|
130
|
+
|
|
131
|
+
@Composable # an identity decorator: the function stays a plain function
|
|
132
|
+
def Screen():
|
|
133
|
+
"""A screen."""
|
|
134
|
+
return "drawn"
|
|
135
|
+
|
|
136
|
+
assert Screen() == "drawn" and Screen.__name__ == "Screen"
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
```python
|
|
140
|
+
import tomllib # the manifest: the one place the mapping is written down
|
|
141
|
+
|
|
142
|
+
with open("pythonx/compose/pythonx-map.toml", "rb") as f:
|
|
143
|
+
manifest = tomllib.load(f)
|
|
144
|
+
|
|
145
|
+
manifest["modules"]["pythonx.compose.layout"]
|
|
146
|
+
# 'androidx.compose.foundation.layout'
|
|
147
|
+
manifest["value-classes"]["raw-primitive-allowed"]
|
|
148
|
+
# ['androidx.compose.ui.unit.Dp'] -> padding(16) means padding(16.dp)
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
```python
|
|
152
|
+
from pythonx.compose._reexport import python_name # the naming rule, no binder needed
|
|
153
|
+
|
|
154
|
+
python_name("fillMaxWidth") # 'fill_max_width'
|
|
155
|
+
python_name("toURLString") # 'to_url_string'
|
|
156
|
+
python_name("Modifier") # 'Modifier'
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
Reading a Compose name such as `pythonx.compose.material3.Text` needs the binder, which the Kotlin
|
|
160
|
+
host installs; outside an app it raises a `RuntimeError` saying so.
|
|
161
|
+
|
|
162
|
+
The tests that drive a `Modifier` chain and overload dispatch read the binder's adaptation layer
|
|
163
|
+
from a sibling [python-multiplatform](https://github.com/thisisthepy/python-multiplatform) checkout
|
|
164
|
+
(or `PYTHONMULTIPLATFORM_HOME`); without one they are skipped.
|
|
165
|
+
|
|
166
|
+
## ๐ฆ Installation
|
|
167
|
+
|
|
168
|
+
Distributed as the pip package **`pythonx-compose`** (Python 3.11 or later), providing the import
|
|
169
|
+
package `pythonx.compose`, with the manifest inside the wheel, and typed: `.pyi` stubs generated from
|
|
170
|
+
real Compose 1.11.1 and `py.typed` ship with it. It runs inside an app that embeds CPython
|
|
171
|
+
through [python-multiplatform](https://github.com/thisisthepy/python-multiplatform); it is not a
|
|
172
|
+
standalone desktop toolkit.
|
|
173
|
+
|
|
174
|
+
## ๐งช Status
|
|
175
|
+
|
|
176
|
+
| Area | State |
|
|
177
|
+
|---|---|
|
|
178
|
+
| Mapping manifest `pythonx-map.toml` | โ
implemented and tested |
|
|
179
|
+
| `@Composable` decorator | โ
implemented and tested |
|
|
180
|
+
| `pythonx` as a real on-disk package re-exporting `androidx.compose.*` by one rule | โ
implemented and tested |
|
|
181
|
+
| `Modifier` chains with snake_case methods, overload dispatch, `Dp` as a number | โ
implemented and tested against the binder's layer |
|
|
182
|
+
| `Column`, `Row`, `Spacer` from `material3` as well as `layout` | โ
implemented and tested |
|
|
183
|
+
| Method keyword arguments in `snake_case` | โ
implemented and tested โ module functions and methods (methods need python-multiplatform's `describe_member`) |
|
|
184
|
+
| The empty `Modifier` | ๐ก partial โ `Modifier.padding(16)` from the class needs an app-supplied factory against real Compose |
|
|
185
|
+
| `Alignment` / `Arrangement` | โ
implemented and tested โ `Alignment.Center`, `Arrangement.spaced_by(8)`, and grouped `Alignment.Horizontal.End` beside `Alignment.End` |
|
|
186
|
+
| Material 3 widgets (`Text`, `Button`, `Card`, `TextField`, โฆ) | ๐ก partial โ re-exported by rule; render proofs per widget pending (#9) |
|
|
187
|
+
| Type stubs (`.pyi`) from real Compose 1.11.1, `py.typed` | โ
implemented and checked with mypy; many types are still `Any` (#12) |
|
|
188
|
+
| Distribution (`pythonx-compose`) | ๐ก partial โ the wheel carries the manifest, the re-export rule, the stubs and `py.typed` |
|
|
189
|
+
| Declared app root (`@app`) and Pythonic state (`state`), no update call | ๐ก partial โ binder path tested against a fake host shaped after python-multiplatform #38; real Compose is the E2E module (#11, #19); numbers and strings round-trip through `state` |
|
|
190
|
+
| `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) |
|
|
191
|
+
| `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 |
|
|
192
|
+
| Colour schemes | โณ planned |
|
|
193
|
+
| `remember_saveable`, coroutine scopes | โณ planned |
|
|
194
|
+
|
|
195
|
+
The full list is on the guide's [Status page](https://thisisthepy.github.io/pythonx-compose/status.html).
|
|
196
|
+
|
|
197
|
+
## ๐ Documentation
|
|
198
|
+
|
|
199
|
+
- **Guide** โ [`docs/guide/`](https://thisisthepy.github.io/pythonx-compose/), bilingual (English / ํ๊ตญ์ด)
|
|
200
|
+
- **Korean README** โ [`docs/locale/README_ko.md`](https://github.com/thisisthepy/pythonx-compose/blob/main/docs/locale/README_ko.md)
|
|
201
|
+
|
|
202
|
+
## ๐ Ecosystem
|
|
203
|
+
|
|
204
|
+
| Repository | Role |
|
|
205
|
+
|---|---|
|
|
206
|
+
| [python-multiplatform](https://github.com/thisisthepy/python-multiplatform) | The binder: CPython embedded in Kotlin Multiplatform, exposing Kotlin to Python under Kotlin names |
|
|
207
|
+
| **pythonx-compose** | Compose, restructured for Python โ this repository |
|
|
208
|
+
| [toolchain](https://github.com/thisisthepy/toolchain) | Gradle build plugin for Python Multiplatform apps |
|
|
209
|
+
| [pypackpack](https://github.com/thisisthepy/pypackpack) | Distributing Python projects across platforms |
|
|
210
|
+
| [torchnative](https://github.com/thisisthepy/torchnative) | Run the real PyTorch ecosystem on device |
|
|
211
|
+
| [Gemstone](https://github.com/LogitAI/Gemstone) | A Kotlin Multiplatform AI app built alongside the stack |
|
|
212
|
+
|
|
213
|
+
## ๐ค Contributing
|
|
214
|
+
|
|
215
|
+
Development is intent-first and test-first: a change starts as a specification change, then a
|
|
216
|
+
failing test, then code. The [guide](https://thisisthepy.github.io/pythonx-compose/status.html) lists what is open and where help is
|
|
217
|
+
useful. Please open an issue before large changes.
|
|
218
|
+
|
|
219
|
+
## Maintainers
|
|
220
|
+
|
|
221
|
+
| Name | Area | Since |
|
|
222
|
+
|---|---|---|
|
|
223
|
+
| [@b-re-w](https://github.com/b-re-w) | Composable runtime | 2023 |
|
|
224
|
+
| [@rnoro5122](https://github.com/rnoro5122) | Material 3 | 2024 |
|
|
225
|
+
|
|
226
|
+
## License
|
|
227
|
+
|
|
228
|
+
[MIT](https://github.com/thisisthepy/pythonx-compose/blob/main/LICENSE) ยฉ 2023โ2024 BREW (b-re-w), Jong-uk Lee (rnoro5122)
|
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
English | [ํ๊ตญ์ด](https://github.com/thisisthepy/pythonx-compose/blob/main/docs/locale/README_ko.md)
|
|
2
|
+
|
|
3
|
+
<div align="center">
|
|
4
|
+
|
|
5
|
+
# pythonx-compose
|
|
6
|
+
|
|
7
|
+
**Write Compose user interfaces in Python โ with Compose's own widgets, spelled the Python way.**
|
|
8
|
+
|
|
9
|
+
[](https://github.com/thisisthepy/pythonx-compose/blob/main/LICENSE)
|
|
10
|
+
[](https://github.com/thisisthepy/pythonx-compose/blob/main/pyproject.toml)
|
|
11
|
+
[](https://github.com/thisisthepy/pythonx-compose/blob/main/pyproject.toml)
|
|
12
|
+
[](#-status)
|
|
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)
|
|
15
|
+
|
|
16
|
+
</div>
|
|
17
|
+
|
|
18
|
+
---
|
|
19
|
+
|
|
20
|
+
## Why
|
|
21
|
+
|
|
22
|
+
Jetpack Compose and Compose Multiplatform are a superb way to build UI โ if you write Kotlin.
|
|
23
|
+
`pythonx-compose` is for people who write Python. It does not reimplement Compose and it does not
|
|
24
|
+
invent a new widget set: it takes the real `androidx.compose.*` API, which
|
|
25
|
+
[python-multiplatform](https://github.com/thisisthepy/python-multiplatform) exposes to Python under
|
|
26
|
+
its Kotlin names, and restructures it into a Pythonic package, `pythonx.compose`.
|
|
27
|
+
|
|
28
|
+
```python
|
|
29
|
+
from pythonx.compose.runtime import Composable
|
|
30
|
+
from pythonx.compose.material3 import Text, Button
|
|
31
|
+
|
|
32
|
+
@Composable
|
|
33
|
+
def Greeting():
|
|
34
|
+
Button(on_click=lambda: print("hi"), content=lambda: Text("Hello from Python"))
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
<sub>These names resolve today, by one rule, inside an app that embeds the binder; per-widget render
|
|
38
|
+
proofs are still pending. See [Status](#-status).</sub>
|
|
39
|
+
|
|
40
|
+
## โจ Principles
|
|
41
|
+
|
|
42
|
+
- **The original API, Pythonic names.** Every parameter is Compose's own parameter, in
|
|
43
|
+
`snake_case`: `onClick` โ `on_click`, `horizontalAlignment` โ `horizontal_alignment`. Upper-case
|
|
44
|
+
names (types, objects, composables) keep their Kotlin spelling; every other name is `snake_case`
|
|
45
|
+
only (`fillMaxWidth` โ `fill_max_width`, `toURLString` โ `to_url_string`), and an explicit
|
|
46
|
+
overload keeps its suffix (`padding__Dp`).
|
|
47
|
+
- **Extension functions are methods.** `Modifier.padding(16).size(24).fill_max_width()` chains
|
|
48
|
+
exactly as it does in Kotlin. Keyword arguments to such a method are `snake_case`
|
|
49
|
+
too (`m.padding(padding_values=...)`), as they are on the module function
|
|
50
|
+
`padding(m, padding_values=...)`; Kotlin's own spelling still works at run time, and the type
|
|
51
|
+
stubs offer only the Pythonic one.
|
|
52
|
+
- **Kotlin objects are namespaces.** `Alignment.Center` and `Arrangement.End` are read without
|
|
53
|
+
parentheses; functions inside them are `snake_case`: `Arrangement.spaced_by(8)`. Constants can
|
|
54
|
+
also be reached grouped by their declared type, as the notebook writes them:
|
|
55
|
+
`Alignment.Horizontal.End` is `Alignment.End`.
|
|
56
|
+
- **`@Composable` stays.** Screens are decorated Python functions.
|
|
57
|
+
- **A real package.** `pythonx/` is ordinary Python source that imports `androidx.compose.*` and
|
|
58
|
+
reshapes it. The binder never renames anything โ renaming is this package's job.
|
|
59
|
+
- **One manifest.** [`pythonx-map.toml`](https://github.com/thisisthepy/pythonx-compose/blob/main/pythonx/compose/pythonx-map.toml) says which `pythonx.compose.*` module
|
|
60
|
+
stands for which Kotlin package. It lives inside the package and ships in the wheel. The runtime
|
|
61
|
+
and the `.pyi` generator read the same file, so what your editor completes is what the interpreter
|
|
62
|
+
resolves.
|
|
63
|
+
|
|
64
|
+
## ๐งฉ Architecture at a glance
|
|
65
|
+
|
|
66
|
+
```mermaid
|
|
67
|
+
flowchart LR
|
|
68
|
+
app["Your Python UI<br/>@Composable def Screen()"] --> px["pythonx.compose<br/>(this package, real Python)"]
|
|
69
|
+
px --> ax["androidx.compose.*<br/>Kotlin names, exposed by python-multiplatform"]
|
|
70
|
+
ax --> compose["Jetpack / Compose Multiplatform"]
|
|
71
|
+
map["pythonx-map.toml"] -.-> px
|
|
72
|
+
map -.-> pyi[".pyi stubs<br/>(in the wheel)"]
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
| Python module | Kotlin package |
|
|
76
|
+
|---|---|
|
|
77
|
+
| `pythonx.compose.runtime` | `androidx.compose.runtime` |
|
|
78
|
+
| `pythonx.compose.ui` | `androidx.compose.ui` |
|
|
79
|
+
| `pythonx.compose.layout` | `androidx.compose.foundation.layout` |
|
|
80
|
+
| `pythonx.compose.material3` | `androidx.compose.material3` |
|
|
81
|
+
|
|
82
|
+
Each module is a real file whose `__init__.py` calls one re-export rule; names resolve on first use.
|
|
83
|
+
`Column`, `Row` and `Spacer` are importable from `pythonx.compose.material3` as well as
|
|
84
|
+
`pythonx.compose.layout`, as the notebook writes them (the manifest's `[aliases]`).
|
|
85
|
+
|
|
86
|
+
## ๐ Quick start
|
|
87
|
+
|
|
88
|
+
> [!NOTE]
|
|
89
|
+
> `pythonx-compose` is **pre-alpha**. The first alpha, `0.1.0a1`, is released to PyPI when the
|
|
90
|
+
> maintainer tags it; a pre-release needs `pip install --pre pythonx-compose`.
|
|
91
|
+
|
|
92
|
+
From a clone, to run the tests:
|
|
93
|
+
|
|
94
|
+
```bash
|
|
95
|
+
git clone https://github.com/thisisthepy/pythonx-compose
|
|
96
|
+
cd pythonx-compose
|
|
97
|
+
python3 -m pip install pytest # inside a virtual environment
|
|
98
|
+
python3 -m pytest tests -q
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
What runs today, from the repository root:
|
|
102
|
+
|
|
103
|
+
```python
|
|
104
|
+
from pythonx.compose.runtime import Composable
|
|
105
|
+
|
|
106
|
+
@Composable # an identity decorator: the function stays a plain function
|
|
107
|
+
def Screen():
|
|
108
|
+
"""A screen."""
|
|
109
|
+
return "drawn"
|
|
110
|
+
|
|
111
|
+
assert Screen() == "drawn" and Screen.__name__ == "Screen"
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
```python
|
|
115
|
+
import tomllib # the manifest: the one place the mapping is written down
|
|
116
|
+
|
|
117
|
+
with open("pythonx/compose/pythonx-map.toml", "rb") as f:
|
|
118
|
+
manifest = tomllib.load(f)
|
|
119
|
+
|
|
120
|
+
manifest["modules"]["pythonx.compose.layout"]
|
|
121
|
+
# 'androidx.compose.foundation.layout'
|
|
122
|
+
manifest["value-classes"]["raw-primitive-allowed"]
|
|
123
|
+
# ['androidx.compose.ui.unit.Dp'] -> padding(16) means padding(16.dp)
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
```python
|
|
127
|
+
from pythonx.compose._reexport import python_name # the naming rule, no binder needed
|
|
128
|
+
|
|
129
|
+
python_name("fillMaxWidth") # 'fill_max_width'
|
|
130
|
+
python_name("toURLString") # 'to_url_string'
|
|
131
|
+
python_name("Modifier") # 'Modifier'
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
Reading a Compose name such as `pythonx.compose.material3.Text` needs the binder, which the Kotlin
|
|
135
|
+
host installs; outside an app it raises a `RuntimeError` saying so.
|
|
136
|
+
|
|
137
|
+
The tests that drive a `Modifier` chain and overload dispatch read the binder's adaptation layer
|
|
138
|
+
from a sibling [python-multiplatform](https://github.com/thisisthepy/python-multiplatform) checkout
|
|
139
|
+
(or `PYTHONMULTIPLATFORM_HOME`); without one they are skipped.
|
|
140
|
+
|
|
141
|
+
## ๐ฆ Installation
|
|
142
|
+
|
|
143
|
+
Distributed as the pip package **`pythonx-compose`** (Python 3.11 or later), providing the import
|
|
144
|
+
package `pythonx.compose`, with the manifest inside the wheel, and typed: `.pyi` stubs generated from
|
|
145
|
+
real Compose 1.11.1 and `py.typed` ship with it. It runs inside an app that embeds CPython
|
|
146
|
+
through [python-multiplatform](https://github.com/thisisthepy/python-multiplatform); it is not a
|
|
147
|
+
standalone desktop toolkit.
|
|
148
|
+
|
|
149
|
+
## ๐งช Status
|
|
150
|
+
|
|
151
|
+
| Area | State |
|
|
152
|
+
|---|---|
|
|
153
|
+
| Mapping manifest `pythonx-map.toml` | โ
implemented and tested |
|
|
154
|
+
| `@Composable` decorator | โ
implemented and tested |
|
|
155
|
+
| `pythonx` as a real on-disk package re-exporting `androidx.compose.*` by one rule | โ
implemented and tested |
|
|
156
|
+
| `Modifier` chains with snake_case methods, overload dispatch, `Dp` as a number | โ
implemented and tested against the binder's layer |
|
|
157
|
+
| `Column`, `Row`, `Spacer` from `material3` as well as `layout` | โ
implemented and tested |
|
|
158
|
+
| Method keyword arguments in `snake_case` | โ
implemented and tested โ module functions and methods (methods need python-multiplatform's `describe_member`) |
|
|
159
|
+
| The empty `Modifier` | ๐ก partial โ `Modifier.padding(16)` from the class needs an app-supplied factory against real Compose |
|
|
160
|
+
| `Alignment` / `Arrangement` | โ
implemented and tested โ `Alignment.Center`, `Arrangement.spaced_by(8)`, and grouped `Alignment.Horizontal.End` beside `Alignment.End` |
|
|
161
|
+
| Material 3 widgets (`Text`, `Button`, `Card`, `TextField`, โฆ) | ๐ก partial โ re-exported by rule; render proofs per widget pending (#9) |
|
|
162
|
+
| Type stubs (`.pyi`) from real Compose 1.11.1, `py.typed` | โ
implemented and checked with mypy; many types are still `Any` (#12) |
|
|
163
|
+
| Distribution (`pythonx-compose`) | ๐ก partial โ the wheel carries the manifest, the re-export rule, the stubs and `py.typed` |
|
|
164
|
+
| Declared app root (`@app`) and Pythonic state (`state`), no update call | ๐ก partial โ binder path tested against a fake host shaped after python-multiplatform #38; real Compose is the E2E module (#11, #19); numbers and strings round-trip through `state` |
|
|
165
|
+
| `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) |
|
|
166
|
+
| `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 |
|
|
167
|
+
| Colour schemes | โณ planned |
|
|
168
|
+
| `remember_saveable`, coroutine scopes | โณ planned |
|
|
169
|
+
|
|
170
|
+
The full list is on the guide's [Status page](https://thisisthepy.github.io/pythonx-compose/status.html).
|
|
171
|
+
|
|
172
|
+
## ๐ Documentation
|
|
173
|
+
|
|
174
|
+
- **Guide** โ [`docs/guide/`](https://thisisthepy.github.io/pythonx-compose/), bilingual (English / ํ๊ตญ์ด)
|
|
175
|
+
- **Korean README** โ [`docs/locale/README_ko.md`](https://github.com/thisisthepy/pythonx-compose/blob/main/docs/locale/README_ko.md)
|
|
176
|
+
|
|
177
|
+
## ๐ Ecosystem
|
|
178
|
+
|
|
179
|
+
| Repository | Role |
|
|
180
|
+
|---|---|
|
|
181
|
+
| [python-multiplatform](https://github.com/thisisthepy/python-multiplatform) | The binder: CPython embedded in Kotlin Multiplatform, exposing Kotlin to Python under Kotlin names |
|
|
182
|
+
| **pythonx-compose** | Compose, restructured for Python โ this repository |
|
|
183
|
+
| [toolchain](https://github.com/thisisthepy/toolchain) | Gradle build plugin for Python Multiplatform apps |
|
|
184
|
+
| [pypackpack](https://github.com/thisisthepy/pypackpack) | Distributing Python projects across platforms |
|
|
185
|
+
| [torchnative](https://github.com/thisisthepy/torchnative) | Run the real PyTorch ecosystem on device |
|
|
186
|
+
| [Gemstone](https://github.com/LogitAI/Gemstone) | A Kotlin Multiplatform AI app built alongside the stack |
|
|
187
|
+
|
|
188
|
+
## ๐ค Contributing
|
|
189
|
+
|
|
190
|
+
Development is intent-first and test-first: a change starts as a specification change, then a
|
|
191
|
+
failing test, then code. The [guide](https://thisisthepy.github.io/pythonx-compose/status.html) lists what is open and where help is
|
|
192
|
+
useful. Please open an issue before large changes.
|
|
193
|
+
|
|
194
|
+
## Maintainers
|
|
195
|
+
|
|
196
|
+
| Name | Area | Since |
|
|
197
|
+
|---|---|---|
|
|
198
|
+
| [@b-re-w](https://github.com/b-re-w) | Composable runtime | 2023 |
|
|
199
|
+
| [@rnoro5122](https://github.com/rnoro5122) | Material 3 | 2024 |
|
|
200
|
+
|
|
201
|
+
## License
|
|
202
|
+
|
|
203
|
+
[MIT](https://github.com/thisisthepy/pythonx-compose/blob/main/LICENSE) ยฉ 2023โ2024 BREW (b-re-w), Jong-uk Lee (rnoro5122)
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=77", "wheel"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "pythonx-compose"
|
|
7
|
+
version = "0.1.0a1"
|
|
8
|
+
description = "Pythonic Compose UI for Python on Kotlin Multiplatform, through python-multiplatform"
|
|
9
|
+
readme = {file = "README.md", content-type = "text/markdown"}
|
|
10
|
+
license = "MIT"
|
|
11
|
+
license-files = ["LICENSE"]
|
|
12
|
+
authors = [
|
|
13
|
+
{name = "BREW (b-re-w)"},
|
|
14
|
+
{name = "Jong-uk Lee (rnoro5122)"},
|
|
15
|
+
]
|
|
16
|
+
keywords = ["compose", "jetpack-compose", "compose-multiplatform", "kotlin-multiplatform", "ui", "python-multiplatform"]
|
|
17
|
+
classifiers = [
|
|
18
|
+
"Development Status :: 3 - Alpha",
|
|
19
|
+
"Intended Audience :: Developers",
|
|
20
|
+
"Operating System :: OS Independent",
|
|
21
|
+
"Programming Language :: Python :: 3",
|
|
22
|
+
"Programming Language :: Python :: 3.11",
|
|
23
|
+
"Programming Language :: Python :: 3.12",
|
|
24
|
+
"Programming Language :: Python :: 3.13",
|
|
25
|
+
"Topic :: Software Development :: User Interfaces",
|
|
26
|
+
"Typing :: Typed",
|
|
27
|
+
]
|
|
28
|
+
dependencies = [
|
|
29
|
+
]
|
|
30
|
+
#dependencies-kotlin = [
|
|
31
|
+
# "org.jetbrains.compose:compose-ui:0.4.0",
|
|
32
|
+
#]
|
|
33
|
+
requires-python = ">= 3.11" # tomllib, which reads the manifest at run time
|
|
34
|
+
#requires-kotlin = ">= 1.4.32"
|
|
35
|
+
|
|
36
|
+
[project.urls]
|
|
37
|
+
Homepage = "https://github.com/thisisthepy/pythonx-compose"
|
|
38
|
+
Source = "https://github.com/thisisthepy/pythonx-compose"
|
|
39
|
+
Issues = "https://github.com/thisisthepy/pythonx-compose/issues"
|
|
40
|
+
Documentation = "https://thisisthepy.github.io/pythonx-compose/"
|
|
41
|
+
|
|
42
|
+
[tool.setuptools.packages.find]
|
|
43
|
+
include = ["pythonx.compose*"]
|
|
44
|
+
|
|
45
|
+
[tool.setuptools.package-data]
|
|
46
|
+
# Every package, not just `pythonx.compose` itself: a package-data key names one package and does
|
|
47
|
+
# not descend, so `"pythonx.compose"` alone would ship `pythonx/compose/*.pyi` and drop
|
|
48
|
+
# `pythonx/compose/ui/*.pyi` -- which is where most of the generated stubs land.
|
|
49
|
+
"*" = ["*.pyi", "py.typed", "pythonx-map.toml"]
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
# GENERATED by scripts/gen_stubs.py from python-multiplatform's Kotlin-named stubs (artifact kotlin-stubs, workflow run 37103737430, commit a5028618, Compose 1.11.1). Do not edit.
|