pythonx-compose 0.1.0a1__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.
- pythonx/compose/__init__.py +4 -0
- pythonx/compose/__init__.pyi +1 -0
- pythonx/compose/_reexport.py +512 -0
- pythonx/compose/foundation/__init__.py +5 -0
- pythonx/compose/foundation/__init__.pyi +17 -0
- pythonx/compose/foundation/layout/__init__.py +5 -0
- pythonx/compose/foundation/layout/__init__.pyi +2 -0
- pythonx/compose/foundation/text/__init__.py +5 -0
- pythonx/compose/foundation/text/input/__init__.py +5 -0
- pythonx/compose/foundation/text/input/__init__.pyi +78 -0
- pythonx/compose/layout/__init__.py +25 -0
- pythonx/compose/layout/__init__.pyi +878 -0
- pythonx/compose/layout/arrangement.py +34 -0
- pythonx/compose/material/__init__.py +8 -0
- pythonx/compose/material/__init__.pyi +1 -0
- pythonx/compose/material/icons/__init__.py +9 -0
- pythonx/compose/material/icons/__init__.pyi +1159 -0
- pythonx/compose/material3/__init__.py +24 -0
- pythonx/compose/material3/__init__.pyi +3305 -0
- pythonx/compose/py.typed +0 -0
- pythonx/compose/pythonx-map.toml +73 -0
- pythonx/compose/runtime/__init__.py +128 -0
- pythonx/compose/runtime/__init__.pyi +180 -0
- pythonx/compose/ui/__init__.py +21 -0
- pythonx/compose/ui/__init__.pyi +975 -0
- pythonx/compose/ui/alignment.py +47 -0
- pythonx/compose/ui/graphics/__init__.py +5 -0
- pythonx/compose/ui/graphics/__init__.pyi +795 -0
- pythonx/compose/ui/graphics/vector/__init__.py +5 -0
- pythonx/compose/ui/graphics/vector/__init__.pyi +221 -0
- pythonx/compose/ui/modifier.py +110 -0
- pythonx/compose/ui/text/__init__.py +5 -0
- pythonx/compose/ui/text/__init__.pyi +703 -0
- pythonx/compose/ui/unit/__init__.py +19 -0
- pythonx/compose/ui/unit/__init__.pyi +348 -0
- pythonx_compose-0.1.0a1.dist-info/METADATA +228 -0
- pythonx_compose-0.1.0a1.dist-info/RECORD +40 -0
- pythonx_compose-0.1.0a1.dist-info/WHEEL +5 -0
- pythonx_compose-0.1.0a1.dist-info/licenses/LICENSE +21 -0
- pythonx_compose-0.1.0a1.dist-info/top_level.txt +1 -0
|
@@ -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.
|
|
@@ -0,0 +1,512 @@
|
|
|
1
|
+
"""The one rule that makes a `pythonx.compose.*` module a Pythonic view of a Kotlin package.
|
|
2
|
+
|
|
3
|
+
The binder (`python-multiplatform`) serves every bound Kotlin package under its **Kotlin** name --
|
|
4
|
+
`androidx.compose.foundation.layout` -- with Kotlin's own surface: declaration names, keyword
|
|
5
|
+
arguments by Kotlin parameter name, Kotlin defaults, overload dispatch. It renames nothing, and it
|
|
6
|
+
publishes what a Pythonic layer needs to rename by rule: `inspect.signature(fn)` with the Kotlin
|
|
7
|
+
parameter names, and `python_multiplatform.describe(fn)` with one dict per overload.
|
|
8
|
+
|
|
9
|
+
This module is that layer. A mapped package's `__init__.py` is one line::
|
|
10
|
+
|
|
11
|
+
__getattr__, __dir__ = reexport(__name__)
|
|
12
|
+
|
|
13
|
+
and every name in it then resolves against the Kotlin package `pythonx-map.toml` maps it to:
|
|
14
|
+
|
|
15
|
+
- **Names.** A name that starts upper-case -- a type, an object, a `@Composable` -- keeps its Kotlin
|
|
16
|
+
spelling (`Modifier`, `Text`). Any other name is reached by its snake_case spelling only
|
|
17
|
+
(`fillMaxWidth` -> `fill_max_width`, `toURLString` -> `to_url_string`), and an explicit overload
|
|
18
|
+
keeps its suffix (`padding__Dp`). The inverse is never computed: the module's real Kotlin names
|
|
19
|
+
are converted forward and looked up, so a name the reverse rule could not invert still resolves.
|
|
20
|
+
- **Keywords.** `on_click=` reaches the parameter Kotlin calls `onClick`, matched against the
|
|
21
|
+
declaration's own parameter names (every overload's), never derived.
|
|
22
|
+
- **Signatures.** A re-exported function answers `inspect.signature` with the snake_case names;
|
|
23
|
+
defaults stay `python_multiplatform.KOTLIN_DEFAULT`.
|
|
24
|
+
- **Value classes.** The manifest's `raw-primitive-allowed` list is handed to the binder once per
|
|
25
|
+
installed binding layer, so `padding(16)` reaches a `Dp` parameter.
|
|
26
|
+
- **Methods on a proxy.** The same name rule is registered as the binder's member resolver, so
|
|
27
|
+
`Modifier.padding(16).fill_max_width()` reaches `fillMaxWidth` on a proxy Kotlin returned, and
|
|
28
|
+
`m.padding(padding_values=...)` reaches `paddingValues`: the resolver also answers a keyword map,
|
|
29
|
+
read from `python_multiplatform.describe_member`. The Kotlin spelling still works. The binder
|
|
30
|
+
caches the alias in its own registry; its proxy classes keep Kotlin names in `dir()`.
|
|
31
|
+
|
|
32
|
+
- **Grouped constants.** Inside a Kotlin object served as a namespace, a type nested in it that
|
|
33
|
+
is not one of its members groups the constants declared as exactly that type, so the notebook's
|
|
34
|
+
`Alignment.Horizontal.End` reads `Alignment.End` (`ConstantGroup`). Declared types come from
|
|
35
|
+
`python_multiplatform.describe(module, name)`, which never runs a constant's getter.
|
|
36
|
+
|
|
37
|
+
A module may also answer for names the manifest's `[aliases]` section lends it from another mapped
|
|
38
|
+
module (`material3` answers for `Column`, `Row`, `Spacer` from `layout`, as the notebook imports
|
|
39
|
+
them); the object is the same one either way. An alias may also lend a name under a different one,
|
|
40
|
+
through an attribute path (`DefaultIcons` is `Icons.Default` of `pythonx.compose.material.icons`,
|
|
41
|
+
INTENT section 5.7); a path with attribute steps is read afresh on every access, as the binder
|
|
42
|
+
serves a live property.
|
|
43
|
+
|
|
44
|
+
Nothing here names a Kotlin declaration. Names defined in the package's own `__init__.py` (such as
|
|
45
|
+
`runtime.Composable`) are ordinary module attributes and win, because `__getattr__` only runs for a
|
|
46
|
+
name the module does not already have.
|
|
47
|
+
"""
|
|
48
|
+
|
|
49
|
+
from __future__ import annotations
|
|
50
|
+
|
|
51
|
+
import importlib
|
|
52
|
+
import importlib.util
|
|
53
|
+
import inspect
|
|
54
|
+
import re
|
|
55
|
+
import sys
|
|
56
|
+
import types
|
|
57
|
+
import weakref
|
|
58
|
+
from functools import lru_cache
|
|
59
|
+
from pathlib import Path
|
|
60
|
+
|
|
61
|
+
ROOT_MODULE = "python_multiplatform"
|
|
62
|
+
BINDING_MODULE = "python_multiplatform.binding"
|
|
63
|
+
|
|
64
|
+
_MANIFEST = Path(__file__).resolve().parent / "pythonx-map.toml"
|
|
65
|
+
|
|
66
|
+
_LOWER_UPPER = re.compile(r"([a-z0-9])([A-Z])")
|
|
67
|
+
_ACRONYM_WORD = re.compile(r"([A-Z]+)([A-Z][a-z])")
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def snake_case(kotlin_name: str) -> str:
|
|
71
|
+
"""`fillMaxWidth` -> `fill_max_width`, `toURLString` -> `to_url_string`, `zIndex` -> `z_index`."""
|
|
72
|
+
return _LOWER_UPPER.sub(r"\1_\2", _ACRONYM_WORD.sub(r"\1_\2", kotlin_name)).lower()
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def python_name(kotlin_name: str) -> str:
|
|
76
|
+
"""The one Pythonic spelling of a Kotlin declaration name.
|
|
77
|
+
|
|
78
|
+
Upper-case names are types, objects and composables and keep their Kotlin spelling. An explicit
|
|
79
|
+
overload (`padding__Dp`) converts its base and keeps the type suffix, which names Kotlin types.
|
|
80
|
+
"""
|
|
81
|
+
if kotlin_name[:1].isupper():
|
|
82
|
+
return kotlin_name
|
|
83
|
+
base, sep, suffix = kotlin_name.partition("__")
|
|
84
|
+
return snake_case(base) + sep + suffix
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
@lru_cache(maxsize=1)
|
|
88
|
+
def manifest() -> dict:
|
|
89
|
+
"""`pythonx-map.toml`, parsed once."""
|
|
90
|
+
import tomllib
|
|
91
|
+
|
|
92
|
+
with _MANIFEST.open("rb") as handle:
|
|
93
|
+
return tomllib.load(handle)
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
def kotlin_package_of(module_name: str) -> str:
|
|
97
|
+
try:
|
|
98
|
+
return manifest()["modules"][module_name]
|
|
99
|
+
except KeyError:
|
|
100
|
+
raise LookupError(
|
|
101
|
+
f"{module_name} has no row in pythonx-map.toml; a re-exported module needs one"
|
|
102
|
+
) from None
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
def binding_layer():
|
|
106
|
+
"""The binder's installed binding module, or a `RuntimeError` that says what did not happen."""
|
|
107
|
+
binding = sys.modules.get(BINDING_MODULE)
|
|
108
|
+
if binding is None:
|
|
109
|
+
raise RuntimeError(
|
|
110
|
+
"the python-multiplatform binding layer is not installed: the Kotlin host must run "
|
|
111
|
+
"PythonxAdapter.install() before pythonx.compose can resolve a Kotlin declaration"
|
|
112
|
+
)
|
|
113
|
+
return binding
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
_PRIMED = weakref.WeakSet()
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
_MEMBER_KEYWORDS: "weakref.WeakKeyDictionary" = weakref.WeakKeyDictionary()
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
def _method_keywords(kotlin_type_name: str, kotlin_member_name: str) -> dict:
|
|
123
|
+
"""`{snake_case(p): p}` for every parameter `p` of every declaration of a proxy's member.
|
|
124
|
+
|
|
125
|
+
Read with `python_multiplatform.describe_member`, which lists each overload's parameters without
|
|
126
|
+
invoking anything (the receiver slot is not among them). It is metadata, so it is kept per
|
|
127
|
+
(type, member), under the `describe_member` of the binding layer that answered: a new layer
|
|
128
|
+
starts with an empty table. A keyword whose snake_case spelling is its own is left out, and a
|
|
129
|
+
binder without `describe_member`, or a member it does not describe, has no map.
|
|
130
|
+
"""
|
|
131
|
+
describe_member = getattr(sys.modules.get(ROOT_MODULE), "describe_member", None)
|
|
132
|
+
if describe_member is None:
|
|
133
|
+
return {}
|
|
134
|
+
try:
|
|
135
|
+
known = _MEMBER_KEYWORDS.setdefault(describe_member, {})
|
|
136
|
+
except TypeError: # something that cannot be weakly referenced: nothing to key a cache by
|
|
137
|
+
known = {}
|
|
138
|
+
key = (kotlin_type_name, kotlin_member_name)
|
|
139
|
+
if key not in known:
|
|
140
|
+
keywords: dict = {}
|
|
141
|
+
try:
|
|
142
|
+
declarations = describe_member(kotlin_type_name, kotlin_member_name)
|
|
143
|
+
except (TypeError, AttributeError):
|
|
144
|
+
declarations = ()
|
|
145
|
+
for declaration in declarations:
|
|
146
|
+
for parameter in declaration.get("parameters", ()):
|
|
147
|
+
kotlin_parameter = parameter.get("name")
|
|
148
|
+
if kotlin_parameter and snake_case(kotlin_parameter) != kotlin_parameter:
|
|
149
|
+
keywords.setdefault(snake_case(kotlin_parameter), kotlin_parameter)
|
|
150
|
+
known[key] = keywords
|
|
151
|
+
return known[key]
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
def member_name(kotlin_type_name: str, requested: str, kotlin_member_names):
|
|
155
|
+
"""The binder's member-resolver hook: the Kotlin member a Pythonic name means on a proxy, and
|
|
156
|
+
the keywords it takes.
|
|
157
|
+
|
|
158
|
+
The binder asks for a name its proxy has no Kotlin member of
|
|
159
|
+
(`python_multiplatform.binding.add_member_resolver`): the rule is the module rule applied
|
|
160
|
+
forward to the proxy's real member names, so `fill_max_width` finds `fillMaxWidth`. It asks
|
|
161
|
+
again for a Kotlin member name when a call passes keywords. The answer is then
|
|
162
|
+
`(kotlin_name, {python_keyword: kotlinParameter})`, the parameters of every overload of that
|
|
163
|
+
member, so `m.padding(padding_values=...)` reaches `paddingValues`. Without a keyword to map the
|
|
164
|
+
answer is the name alone, which is all a binder without `describe_member` can be given.
|
|
165
|
+
"""
|
|
166
|
+
if requested in kotlin_member_names:
|
|
167
|
+
target = requested
|
|
168
|
+
else:
|
|
169
|
+
matches = [name for name in kotlin_member_names if python_name(name) == requested]
|
|
170
|
+
target = matches[0] if len(matches) == 1 else None
|
|
171
|
+
if target is None:
|
|
172
|
+
return None
|
|
173
|
+
keywords = _method_keywords(kotlin_type_name, target)
|
|
174
|
+
return (target, keywords) if keywords else target
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
def _prime(binding) -> None:
|
|
178
|
+
"""Hand this binding layer the manifest's value-class allowlist and the member rule, once.
|
|
179
|
+
|
|
180
|
+
The member rule needs python-multiplatform's `add_member_resolver`; on a binder without it,
|
|
181
|
+
methods on a returned proxy stay reachable by their Kotlin names only.
|
|
182
|
+
"""
|
|
183
|
+
if binding in _PRIMED:
|
|
184
|
+
return
|
|
185
|
+
for kotlin_type in manifest().get("value-classes", {}).get("raw-primitive-allowed", ()):
|
|
186
|
+
binding.allow_raw_primitive(kotlin_type)
|
|
187
|
+
add_member_resolver = getattr(binding, "add_member_resolver", None)
|
|
188
|
+
if add_member_resolver is not None:
|
|
189
|
+
add_member_resolver(member_name)
|
|
190
|
+
_PRIMED.add(binding)
|
|
191
|
+
|
|
192
|
+
|
|
193
|
+
def kotlin_module(module_name: str):
|
|
194
|
+
"""The binder's module for the Kotlin package `module_name` maps to."""
|
|
195
|
+
_prime(binding_layer())
|
|
196
|
+
return importlib.import_module(kotlin_package_of(module_name))
|
|
197
|
+
|
|
198
|
+
|
|
199
|
+
def _name_table(kotlin) -> dict:
|
|
200
|
+
"""Pythonic name -> Kotlin name for everything the Kotlin module reports, with collisions refused."""
|
|
201
|
+
table = {}
|
|
202
|
+
for kotlin_name in dir(kotlin):
|
|
203
|
+
if kotlin_name.startswith("_"):
|
|
204
|
+
continue
|
|
205
|
+
pythonic = python_name(kotlin_name)
|
|
206
|
+
other = table.get(pythonic)
|
|
207
|
+
if other is not None and other != kotlin_name:
|
|
208
|
+
raise AttributeError(
|
|
209
|
+
f"{pythonic!r} would name both {other!r} and {kotlin_name!r} in {kotlin.__name__}; "
|
|
210
|
+
"reach one by its explicit overload spelling"
|
|
211
|
+
)
|
|
212
|
+
table[pythonic] = kotlin_name
|
|
213
|
+
return table
|
|
214
|
+
|
|
215
|
+
|
|
216
|
+
def _declared_type(kotlin, kotlin_name: str):
|
|
217
|
+
"""`(kind, declared return type)` of the bound name [kotlin_name] in [kotlin], without reading it.
|
|
218
|
+
|
|
219
|
+
`python_multiplatform.describe(module, name)` answers from the binder's table and never runs a
|
|
220
|
+
constant's getter (python-multiplatform #36). A binder without the two-argument form, or a name
|
|
221
|
+
that is not a single bound declaration (an overload set, a child package), answers `None`.
|
|
222
|
+
"""
|
|
223
|
+
root = sys.modules.get(ROOT_MODULE)
|
|
224
|
+
describe = getattr(root, "describe", None)
|
|
225
|
+
if describe is None:
|
|
226
|
+
return None
|
|
227
|
+
try:
|
|
228
|
+
described = describe(kotlin, kotlin_name)
|
|
229
|
+
except (TypeError, AttributeError):
|
|
230
|
+
return None
|
|
231
|
+
if not isinstance(described, dict):
|
|
232
|
+
return None
|
|
233
|
+
return described.get("kind"), described.get("returns")
|
|
234
|
+
|
|
235
|
+
|
|
236
|
+
def _describe(fn):
|
|
237
|
+
root = sys.modules.get(ROOT_MODULE)
|
|
238
|
+
if root is None or not hasattr(root, "describe"):
|
|
239
|
+
return ()
|
|
240
|
+
try:
|
|
241
|
+
return root.describe(fn)
|
|
242
|
+
except (TypeError, AttributeError):
|
|
243
|
+
return ()
|
|
244
|
+
|
|
245
|
+
|
|
246
|
+
class PythonicFunction:
|
|
247
|
+
"""A binder callable seen through the naming rule: snake_case keywords and signature.
|
|
248
|
+
|
|
249
|
+
Calls go straight to the binder's callable with the keywords translated; overload selection,
|
|
250
|
+
defaults and value classes are the binder's. A keyword that matches no parameter is passed on
|
|
251
|
+
unchanged, so the binder's own refusal -- which lists the candidates -- is what the caller sees.
|
|
252
|
+
"""
|
|
253
|
+
|
|
254
|
+
__slots__ = ("_kotlin", "_keywords", "__name__", "__qualname__")
|
|
255
|
+
|
|
256
|
+
def __init__(self, kotlin_fn, name):
|
|
257
|
+
self._kotlin = kotlin_fn
|
|
258
|
+
self.__name__ = name
|
|
259
|
+
self.__qualname__ = name
|
|
260
|
+
keywords = {}
|
|
261
|
+
for row in _describe(kotlin_fn):
|
|
262
|
+
for parameter in row.get("parameters", ()):
|
|
263
|
+
kotlin_parameter = parameter["name"]
|
|
264
|
+
keywords.setdefault(snake_case(kotlin_parameter), kotlin_parameter)
|
|
265
|
+
self._keywords = keywords
|
|
266
|
+
|
|
267
|
+
@property
|
|
268
|
+
def __kotlin__(self):
|
|
269
|
+
"""The binder's callable this wraps."""
|
|
270
|
+
return self._kotlin
|
|
271
|
+
|
|
272
|
+
@property
|
|
273
|
+
def __kotlin_rows__(self):
|
|
274
|
+
return self._kotlin.__kotlin_rows__
|
|
275
|
+
|
|
276
|
+
@property
|
|
277
|
+
def __signature__(self):
|
|
278
|
+
kotlin_signature = inspect.signature(self._kotlin)
|
|
279
|
+
return kotlin_signature.replace(parameters=[
|
|
280
|
+
parameter if parameter.kind in (parameter.VAR_POSITIONAL, parameter.VAR_KEYWORD)
|
|
281
|
+
else parameter.replace(name=snake_case(parameter.name))
|
|
282
|
+
for parameter in kotlin_signature.parameters.values()
|
|
283
|
+
])
|
|
284
|
+
|
|
285
|
+
def __call__(self, *args, **kwargs):
|
|
286
|
+
if kwargs:
|
|
287
|
+
kwargs = {self._keywords.get(key, key): value for key, value in kwargs.items()}
|
|
288
|
+
return self._kotlin(*args, **kwargs)
|
|
289
|
+
|
|
290
|
+
def __repr__(self):
|
|
291
|
+
return f"<pythonx function {self.__name__} -> {self._kotlin!r}>"
|
|
292
|
+
|
|
293
|
+
|
|
294
|
+
def _pythonic(value, name):
|
|
295
|
+
if isinstance(value, types.ModuleType):
|
|
296
|
+
# A Kotlin object served as a sub-package. Since python-multiplatform #35 the binder lists it
|
|
297
|
+
# and exposes it as an attribute of its parent, so the name table reaches it directly.
|
|
298
|
+
return KotlinObject(value)
|
|
299
|
+
if isinstance(value, type) or not callable(value):
|
|
300
|
+
return value
|
|
301
|
+
return PythonicFunction(value, name)
|
|
302
|
+
|
|
303
|
+
|
|
304
|
+
def _resolve(kotlin, name: str):
|
|
305
|
+
"""What the Pythonic [name] means in the binder's module [kotlin]: `(value, cacheable)` or `None`.
|
|
306
|
+
|
|
307
|
+
A name the module lists is looked up by the rule. An upper-case name it does not list may be a
|
|
308
|
+
Kotlin object served as a sub-package (`androidx.compose.ui.Alignment`), which the binder imports
|
|
309
|
+
on demand but does not list or expose as an attribute before that (python-multiplatform #35);
|
|
310
|
+
it comes back as a `KotlinObject` namespace read by the same rule.
|
|
311
|
+
"""
|
|
312
|
+
kotlin_name = _name_table(kotlin).get(name)
|
|
313
|
+
if kotlin_name is not None:
|
|
314
|
+
value = getattr(kotlin, kotlin_name)
|
|
315
|
+
# The binder declined to cache it -- a live property, read again on every access -- so this
|
|
316
|
+
# layer does not freeze it either.
|
|
317
|
+
return _pythonic(value, name), kotlin_name in vars(kotlin)
|
|
318
|
+
if name[:1].isupper():
|
|
319
|
+
try:
|
|
320
|
+
nested = importlib.import_module(kotlin.__name__ + "." + name)
|
|
321
|
+
except ImportError:
|
|
322
|
+
return None
|
|
323
|
+
return KotlinObject(nested), True
|
|
324
|
+
return None
|
|
325
|
+
|
|
326
|
+
|
|
327
|
+
_STATIC_GETTER = "STATIC_GETTER"
|
|
328
|
+
|
|
329
|
+
|
|
330
|
+
def _constant_groups(kotlin) -> dict:
|
|
331
|
+
"""Group name -> {Pythonic constant name: Kotlin name}, for the Kotlin object [kotlin].
|
|
332
|
+
|
|
333
|
+
A group is a type nested in the object, `P.G`, that some constant of `P` is declared as; it holds
|
|
334
|
+
exactly the constants declared as `P.G` (SPEC S7.1). A constant declared as `P` itself or as any
|
|
335
|
+
other type is in no group. Declared types come from `describe(module, name)`, never from a value.
|
|
336
|
+
A name the object already has is a Kotlin member and wins, so it is never a group.
|
|
337
|
+
"""
|
|
338
|
+
prefix = kotlin.__name__ + "."
|
|
339
|
+
members = _name_table(kotlin)
|
|
340
|
+
groups: dict = {}
|
|
341
|
+
for pythonic, kotlin_name in members.items():
|
|
342
|
+
declared = _declared_type(kotlin, kotlin_name)
|
|
343
|
+
if declared is None or declared[0] != _STATIC_GETTER or not declared[1]:
|
|
344
|
+
continue
|
|
345
|
+
group = declared[1][len(prefix):] if declared[1].startswith(prefix) else ""
|
|
346
|
+
if not group[:1].isupper() or "." in group or group in members:
|
|
347
|
+
continue
|
|
348
|
+
groups.setdefault(group, {})[pythonic] = kotlin_name
|
|
349
|
+
return groups
|
|
350
|
+
|
|
351
|
+
|
|
352
|
+
class KotlinObject:
|
|
353
|
+
"""A Kotlin object (`Arrangement`, `Alignment`) served as a namespace, read by the module rule.
|
|
354
|
+
|
|
355
|
+
Constants are read again on every access, as the binder serves them; functions inside the object
|
|
356
|
+
are snake_case (`Arrangement.spaced_by`) and are resolved once. A type nested in the object that
|
|
357
|
+
is no member of it groups the constants declared as that type (`Alignment.Horizontal.End`,
|
|
358
|
+
`ConstantGroup`); which constants those are is worked out once per object, from declared types.
|
|
359
|
+
"""
|
|
360
|
+
|
|
361
|
+
__slots__ = ("_kotlin", "_cache", "_groups")
|
|
362
|
+
|
|
363
|
+
def __init__(self, kotlin_module):
|
|
364
|
+
object.__setattr__(self, "_kotlin", kotlin_module)
|
|
365
|
+
object.__setattr__(self, "_cache", {})
|
|
366
|
+
object.__setattr__(self, "_groups", None)
|
|
367
|
+
|
|
368
|
+
def _constant_groups(self) -> dict:
|
|
369
|
+
if self._groups is None:
|
|
370
|
+
object.__setattr__(self, "_groups", _constant_groups(self._kotlin))
|
|
371
|
+
return self._groups
|
|
372
|
+
|
|
373
|
+
def __getattr__(self, name):
|
|
374
|
+
if name.startswith("__") and name.endswith("__"):
|
|
375
|
+
raise AttributeError(name)
|
|
376
|
+
cached = self._cache.get(name)
|
|
377
|
+
if cached is not None:
|
|
378
|
+
return cached
|
|
379
|
+
resolved = _resolve(self._kotlin, name)
|
|
380
|
+
if resolved is None:
|
|
381
|
+
members = self._constant_groups().get(name) if name[:1].isupper() else None
|
|
382
|
+
if members is None:
|
|
383
|
+
raise AttributeError(
|
|
384
|
+
f"{self._kotlin.__name__} has nothing spelled {name!r} under pythonx's naming rule"
|
|
385
|
+
)
|
|
386
|
+
# The grouping is metadata and is kept; the constants inside are read on each access.
|
|
387
|
+
resolved = ConstantGroup(self, f"{self._kotlin.__name__}.{name}", members), True
|
|
388
|
+
value, cacheable = resolved
|
|
389
|
+
if cacheable:
|
|
390
|
+
self._cache[name] = value
|
|
391
|
+
return value
|
|
392
|
+
|
|
393
|
+
def __setattr__(self, name, value):
|
|
394
|
+
raise AttributeError(f"{self._kotlin.__name__} is a Kotlin object; its members are read-only here")
|
|
395
|
+
|
|
396
|
+
def __dir__(self):
|
|
397
|
+
return sorted(set(_name_table(self._kotlin)) | set(self._constant_groups()))
|
|
398
|
+
|
|
399
|
+
def __repr__(self):
|
|
400
|
+
return f"<pythonx view of Kotlin object {self._kotlin.__name__}>"
|
|
401
|
+
|
|
402
|
+
|
|
403
|
+
class ConstantGroup:
|
|
404
|
+
"""The constants of a Kotlin object declared as one type nested in it: `Alignment.Horizontal`.
|
|
405
|
+
|
|
406
|
+
The notebook's grouped spelling (INTENT section 5.3). Reading `Alignment.Horizontal.End` reads
|
|
407
|
+
`Alignment.End` through its `KotlinObject`, so both spellings are the same live read of the same
|
|
408
|
+
Kotlin getter. Only constants whose *declared* type is exactly this one are here.
|
|
409
|
+
"""
|
|
410
|
+
|
|
411
|
+
__slots__ = ("_owner", "_kotlin_type", "_members")
|
|
412
|
+
|
|
413
|
+
def __init__(self, owner, kotlin_type, members):
|
|
414
|
+
object.__setattr__(self, "_owner", owner)
|
|
415
|
+
object.__setattr__(self, "_kotlin_type", kotlin_type)
|
|
416
|
+
object.__setattr__(self, "_members", dict(members))
|
|
417
|
+
|
|
418
|
+
def __getattr__(self, name):
|
|
419
|
+
if name.startswith("__") and name.endswith("__"):
|
|
420
|
+
raise AttributeError(name)
|
|
421
|
+
if name not in self._members:
|
|
422
|
+
raise AttributeError(
|
|
423
|
+
f"{self._kotlin_type} groups no constant spelled {name!r}; it holds the constants of "
|
|
424
|
+
f"{self._owner._kotlin.__name__} declared as {self._kotlin_type}: "
|
|
425
|
+
+ ", ".join(sorted(self._members))
|
|
426
|
+
)
|
|
427
|
+
return getattr(self._owner, name)
|
|
428
|
+
|
|
429
|
+
def __setattr__(self, name, value):
|
|
430
|
+
raise AttributeError(f"{self._kotlin_type} is a group of Kotlin constants; they are read-only here")
|
|
431
|
+
|
|
432
|
+
def __dir__(self):
|
|
433
|
+
return sorted(self._members)
|
|
434
|
+
|
|
435
|
+
def __repr__(self):
|
|
436
|
+
return f"<pythonx group of {self._owner._kotlin.__name__} constants declared {self._kotlin_type}>"
|
|
437
|
+
|
|
438
|
+
|
|
439
|
+
def _aliases(module_name: str) -> dict:
|
|
440
|
+
"""Name -> `(source module, attribute path in it)`, per the manifest's `[aliases]`.
|
|
441
|
+
|
|
442
|
+
An entry under a source module is a list of names (lent under the same name, path = the name) or
|
|
443
|
+
a table `{ lent = "Path.To.Attr" }` (a different name, through an attribute path).
|
|
444
|
+
"""
|
|
445
|
+
borrowed = {}
|
|
446
|
+
for source, lent in manifest().get("aliases", {}).get(module_name, {}).items():
|
|
447
|
+
pairs = ((name, name) for name in lent) if isinstance(lent, list) else lent.items()
|
|
448
|
+
for name, path in pairs:
|
|
449
|
+
borrowed[name] = (source, path)
|
|
450
|
+
return borrowed
|
|
451
|
+
|
|
452
|
+
|
|
453
|
+
def _borrow(module_name: str, name: str, source: str, path: str):
|
|
454
|
+
"""The value of the alias [name]: [path] read in [source].
|
|
455
|
+
|
|
456
|
+
A bare name is resolved once and kept in the owner's namespace. A path with attribute steps is
|
|
457
|
+
read on every access and not kept, because what it ends in may be a live property -- the binder
|
|
458
|
+
serves `Icons.Default` afresh on every read and this layer does not freeze it either.
|
|
459
|
+
"""
|
|
460
|
+
head, *rest = path.split(".")
|
|
461
|
+
value = getattr(importlib.import_module(source), head)
|
|
462
|
+
for step in rest:
|
|
463
|
+
value = getattr(value, step)
|
|
464
|
+
if not rest:
|
|
465
|
+
setattr(sys.modules[module_name], name, value)
|
|
466
|
+
return value
|
|
467
|
+
|
|
468
|
+
|
|
469
|
+
def reexport(module_name: str):
|
|
470
|
+
"""The module-level `__getattr__` and `__dir__` for the mapped package `module_name`.
|
|
471
|
+
|
|
472
|
+
Nothing is resolved here, only when a name is first read, so importing a `pythonx.compose`
|
|
473
|
+
module needs neither the manifest row's Kotlin package nor an installed binder.
|
|
474
|
+
"""
|
|
475
|
+
|
|
476
|
+
def __getattr__(name):
|
|
477
|
+
if name.startswith("__") and name.endswith("__"):
|
|
478
|
+
raise AttributeError(name)
|
|
479
|
+
if importlib.util.find_spec(module_name + "." + name) is not None:
|
|
480
|
+
# A submodule file of this package (`ui.modifier`). `from package import name` asks for
|
|
481
|
+
# the attribute before it imports a submodule, and only an AttributeError lets it
|
|
482
|
+
# fall back -- so this has to answer before anything that needs the binder.
|
|
483
|
+
return importlib.import_module(module_name + "." + name)
|
|
484
|
+
borrowed = _aliases(module_name).get(name)
|
|
485
|
+
if borrowed is not None:
|
|
486
|
+
return _borrow(module_name, name, *borrowed)
|
|
487
|
+
try:
|
|
488
|
+
kotlin = kotlin_module(module_name)
|
|
489
|
+
except ImportError as missing:
|
|
490
|
+
raise AttributeError(
|
|
491
|
+
f"module {module_name!r} has no attribute {name!r} ({missing})"
|
|
492
|
+
) from missing
|
|
493
|
+
resolved = _resolve(kotlin, name)
|
|
494
|
+
if resolved is None:
|
|
495
|
+
raise AttributeError(
|
|
496
|
+
f"module {module_name!r} has no attribute {name!r} (nothing in Kotlin package "
|
|
497
|
+
f"{kotlin.__name__} is spelled that way under pythonx's naming rule)"
|
|
498
|
+
)
|
|
499
|
+
value, cacheable = resolved
|
|
500
|
+
if cacheable:
|
|
501
|
+
setattr(sys.modules[module_name], name, value)
|
|
502
|
+
return value
|
|
503
|
+
|
|
504
|
+
def __dir__():
|
|
505
|
+
own = set(vars(sys.modules[module_name])) | set(_aliases(module_name))
|
|
506
|
+
try:
|
|
507
|
+
own.update(_name_table(kotlin_module(module_name)))
|
|
508
|
+
except (RuntimeError, ImportError):
|
|
509
|
+
pass
|
|
510
|
+
return sorted(own)
|
|
511
|
+
|
|
512
|
+
return __getattr__, __dir__
|
|
@@ -0,0 +1,17 @@
|
|
|
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.
|
|
2
|
+
import typing as _t
|
|
3
|
+
|
|
4
|
+
class BorderStroke:
|
|
5
|
+
"""Kotlin: androidx.compose.foundation.BorderStroke"""
|
|
6
|
+
|
|
7
|
+
class Indication:
|
|
8
|
+
"""Kotlin: androidx.compose.foundation.Indication"""
|
|
9
|
+
|
|
10
|
+
class IndicationNodeFactory(Indication):
|
|
11
|
+
"""Kotlin: androidx.compose.foundation.IndicationNodeFactory"""
|
|
12
|
+
|
|
13
|
+
class MutatorMutex:
|
|
14
|
+
"""Kotlin: androidx.compose.foundation.MutatorMutex"""
|
|
15
|
+
|
|
16
|
+
class ScrollState:
|
|
17
|
+
"""Kotlin: androidx.compose.foundation.ScrollState"""
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
"""`pythonx.compose.foundation.text` -- only the container of `pythonx.compose.foundation.text.input`.
|
|
2
|
+
|
|
3
|
+
No manifest row maps `androidx.compose.foundation.text` itself; this file exists so the package
|
|
4
|
+
is found by the wheel (`packages.find` does not descend into a directory without one).
|
|
5
|
+
"""
|
|
@@ -0,0 +1,78 @@
|
|
|
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.
|
|
2
|
+
import typing as _t
|
|
3
|
+
|
|
4
|
+
class _TextFieldState_clearText(_t.Protocol):
|
|
5
|
+
def __call__(self) -> None: ...
|
|
6
|
+
|
|
7
|
+
class _TextFieldState_setTextAndPlaceCursorAtEnd(_t.Protocol):
|
|
8
|
+
def __call__(self, text: str) -> None: ...
|
|
9
|
+
|
|
10
|
+
class _TextFieldState_setTextAndSelectAll(_t.Protocol):
|
|
11
|
+
def __call__(self, text: str) -> None: ...
|
|
12
|
+
|
|
13
|
+
class _TextFieldState_toTextFieldBuffer(_t.Protocol):
|
|
14
|
+
def __call__(self) -> TextFieldBuffer: ...
|
|
15
|
+
|
|
16
|
+
class InputTransformation:
|
|
17
|
+
"""Kotlin: androidx.compose.foundation.text.input.InputTransformation"""
|
|
18
|
+
|
|
19
|
+
class KeyboardActionHandler:
|
|
20
|
+
"""Kotlin: androidx.compose.foundation.text.input.KeyboardActionHandler"""
|
|
21
|
+
|
|
22
|
+
class OutputTransformation:
|
|
23
|
+
"""Kotlin: androidx.compose.foundation.text.input.OutputTransformation"""
|
|
24
|
+
|
|
25
|
+
class TextFieldBuffer:
|
|
26
|
+
"""Kotlin: androidx.compose.foundation.text.input.TextFieldBuffer"""
|
|
27
|
+
|
|
28
|
+
class TextFieldLineLimits:
|
|
29
|
+
"""Kotlin: androidx.compose.foundation.text.input.TextFieldLineLimits"""
|
|
30
|
+
|
|
31
|
+
class TextFieldState:
|
|
32
|
+
"""Kotlin: androidx.compose.foundation.text.input.TextFieldState"""
|
|
33
|
+
def __init__(self, initialText: str = ..., initialSelection: _t.Any = ...) -> None:
|
|
34
|
+
"""Kotlin constructor: androidx.compose.foundation.text.input.TextFieldState (androidx.compose.foundation.text.input.TextFieldState(initialText: kotlin.String, initialSelection: androidx.compose.ui.text.TextRange): androidx.compose.foundation.text.input.TextFieldState)"""
|
|
35
|
+
...
|
|
36
|
+
clear_text: _t.ClassVar[_TextFieldState_clearText]
|
|
37
|
+
set_text_and_place_cursor_at_end: _t.ClassVar[_TextFieldState_setTextAndPlaceCursorAtEnd]
|
|
38
|
+
set_text_and_select_all: _t.ClassVar[_TextFieldState_setTextAndSelectAll]
|
|
39
|
+
to_text_field_buffer: _t.ClassVar[_TextFieldState_toTextFieldBuffer]
|
|
40
|
+
@property
|
|
41
|
+
def composition(self) -> _t.Any | None:
|
|
42
|
+
"""Kotlin property: androidx.compose.foundation.text.input.TextFieldState.composition (androidx.compose.foundation.text.input.TextFieldState.composition: androidx.compose.ui.text.TextRange?)"""
|
|
43
|
+
...
|
|
44
|
+
@property
|
|
45
|
+
def selection(self) -> _t.Any:
|
|
46
|
+
"""Kotlin property: androidx.compose.foundation.text.input.TextFieldState.selection (androidx.compose.foundation.text.input.TextFieldState.selection: androidx.compose.ui.text.TextRange)"""
|
|
47
|
+
...
|
|
48
|
+
@property
|
|
49
|
+
def text(self) -> str:
|
|
50
|
+
"""Kotlin property: androidx.compose.foundation.text.input.TextFieldState.text (androidx.compose.foundation.text.input.TextFieldState.text: kotlin.CharSequence)"""
|
|
51
|
+
...
|
|
52
|
+
@property
|
|
53
|
+
def undo_state(self) -> UndoState:
|
|
54
|
+
"""Kotlin property: androidx.compose.foundation.text.input.TextFieldState.undoState (androidx.compose.foundation.text.input.TextFieldState.undoState: androidx.compose.foundation.text.input.UndoState)"""
|
|
55
|
+
...
|
|
56
|
+
|
|
57
|
+
class UndoState:
|
|
58
|
+
"""Kotlin: androidx.compose.foundation.text.input.UndoState"""
|
|
59
|
+
|
|
60
|
+
def clear_text(receiver: TextFieldState, /) -> None:
|
|
61
|
+
"""Kotlin: androidx.compose.foundation.text.input.TextFieldState.clearText(): kotlin.Unit"""
|
|
62
|
+
...
|
|
63
|
+
|
|
64
|
+
def remember_text_field_state(initial_text: str = ..., initial_selection: _t.Any = ...) -> TextFieldState:
|
|
65
|
+
"""Kotlin: androidx.compose.foundation.text.input.rememberTextFieldState(initialText: kotlin.String, initialSelection: androidx.compose.ui.text.TextRange): androidx.compose.foundation.text.input.TextFieldState"""
|
|
66
|
+
...
|
|
67
|
+
|
|
68
|
+
def set_text_and_place_cursor_at_end(receiver: TextFieldState, /, text: str) -> None:
|
|
69
|
+
"""Kotlin: androidx.compose.foundation.text.input.TextFieldState.setTextAndPlaceCursorAtEnd(text: kotlin.String): kotlin.Unit"""
|
|
70
|
+
...
|
|
71
|
+
|
|
72
|
+
def set_text_and_select_all(receiver: TextFieldState, /, text: str) -> None:
|
|
73
|
+
"""Kotlin: androidx.compose.foundation.text.input.TextFieldState.setTextAndSelectAll(text: kotlin.String): kotlin.Unit"""
|
|
74
|
+
...
|
|
75
|
+
|
|
76
|
+
def to_text_field_buffer(receiver: TextFieldState, /) -> TextFieldBuffer:
|
|
77
|
+
"""Kotlin: androidx.compose.foundation.text.input.TextFieldState.toTextFieldBuffer(): androidx.compose.foundation.text.input.TextFieldBuffer"""
|
|
78
|
+
...
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
"""`pythonx/compose/layout/__init__.py` -- the Pythonic view of `androidx.compose.foundation.layout`.
|
|
2
|
+
|
|
3
|
+
## What used to be here, and why it is gone
|
|
4
|
+
|
|
5
|
+
A try...except block executing relative import `from .arrangement import Arrangement`. `arrangement.py`
|
|
6
|
+
is a docstring module (no `Arrangement` class is declared inside it). Attempting to import `Arrangement`
|
|
7
|
+
from `.arrangement` raised `ImportError`.
|
|
8
|
+
|
|
9
|
+
## How `pythonx.compose.layout` resolves
|
|
10
|
+
|
|
11
|
+
`import pythonx.compose.layout` loads this file. The binder serves the declarations (`Arrangement`,
|
|
12
|
+
`Column`, `Row`, `Spacer`, ...) under their Kotlin package, `androidx.compose.foundation.layout`, and
|
|
13
|
+
renames nothing; this file re-exports them under Pythonic names by the one rule in
|
|
14
|
+
`pythonx/compose/_reexport.py` (SPEC section 3).
|
|
15
|
+
|
|
16
|
+
## Calling convention for constants
|
|
17
|
+
|
|
18
|
+
Constants on layout objects (e.g., `Arrangement.Start`, `Arrangement.Center`, `Arrangement.SpaceBetween`)
|
|
19
|
+
are read as attributes, **without parentheses** (`Arrangement.Start`, `TextStyle.Default`), since
|
|
20
|
+
PythonMultiplatform `46be0212`.
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
from pythonx.compose._reexport import reexport
|
|
24
|
+
|
|
25
|
+
__getattr__, __dir__ = reexport(__name__)
|