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.
Files changed (40) hide show
  1. pythonx/compose/__init__.py +4 -0
  2. pythonx/compose/__init__.pyi +1 -0
  3. pythonx/compose/_reexport.py +512 -0
  4. pythonx/compose/foundation/__init__.py +5 -0
  5. pythonx/compose/foundation/__init__.pyi +17 -0
  6. pythonx/compose/foundation/layout/__init__.py +5 -0
  7. pythonx/compose/foundation/layout/__init__.pyi +2 -0
  8. pythonx/compose/foundation/text/__init__.py +5 -0
  9. pythonx/compose/foundation/text/input/__init__.py +5 -0
  10. pythonx/compose/foundation/text/input/__init__.pyi +78 -0
  11. pythonx/compose/layout/__init__.py +25 -0
  12. pythonx/compose/layout/__init__.pyi +878 -0
  13. pythonx/compose/layout/arrangement.py +34 -0
  14. pythonx/compose/material/__init__.py +8 -0
  15. pythonx/compose/material/__init__.pyi +1 -0
  16. pythonx/compose/material/icons/__init__.py +9 -0
  17. pythonx/compose/material/icons/__init__.pyi +1159 -0
  18. pythonx/compose/material3/__init__.py +24 -0
  19. pythonx/compose/material3/__init__.pyi +3305 -0
  20. pythonx/compose/py.typed +0 -0
  21. pythonx/compose/pythonx-map.toml +73 -0
  22. pythonx/compose/runtime/__init__.py +128 -0
  23. pythonx/compose/runtime/__init__.pyi +180 -0
  24. pythonx/compose/ui/__init__.py +21 -0
  25. pythonx/compose/ui/__init__.pyi +975 -0
  26. pythonx/compose/ui/alignment.py +47 -0
  27. pythonx/compose/ui/graphics/__init__.py +5 -0
  28. pythonx/compose/ui/graphics/__init__.pyi +795 -0
  29. pythonx/compose/ui/graphics/vector/__init__.py +5 -0
  30. pythonx/compose/ui/graphics/vector/__init__.pyi +221 -0
  31. pythonx/compose/ui/modifier.py +110 -0
  32. pythonx/compose/ui/text/__init__.py +5 -0
  33. pythonx/compose/ui/text/__init__.pyi +703 -0
  34. pythonx/compose/ui/unit/__init__.py +19 -0
  35. pythonx/compose/ui/unit/__init__.pyi +348 -0
  36. pythonx_compose-0.1.0a1.dist-info/METADATA +228 -0
  37. pythonx_compose-0.1.0a1.dist-info/RECORD +40 -0
  38. pythonx_compose-0.1.0a1.dist-info/WHEEL +5 -0
  39. pythonx_compose-0.1.0a1.dist-info/licenses/LICENSE +21 -0
  40. pythonx_compose-0.1.0a1.dist-info/top_level.txt +1 -0
@@ -0,0 +1,4 @@
1
+
2
+ from pythonx.compose._reexport import reexport
3
+
4
+ __getattr__, __dir__ = reexport(__name__)
@@ -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,5 @@
1
+ """`pythonx.compose.foundation` -- the Pythonic view of `androidx.compose.foundation` (see `pythonx/compose/_reexport.py`)."""
2
+
3
+ from pythonx.compose._reexport import reexport
4
+
5
+ __getattr__, __dir__ = reexport(__name__)
@@ -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.layout` -- the Pythonic view of `androidx.compose.foundation.layout` (see `pythonx/compose/_reexport.py`)."""
2
+
3
+ from pythonx.compose._reexport import reexport
4
+
5
+ __getattr__, __dir__ = reexport(__name__)
@@ -0,0 +1,2 @@
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
+ from pythonx.compose.layout import *
@@ -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,5 @@
1
+ """`pythonx.compose.foundation.text.input` -- the Pythonic view of `androidx.compose.foundation.text.input` (see `pythonx/compose/_reexport.py`)."""
2
+
3
+ from pythonx.compose._reexport import reexport
4
+
5
+ __getattr__, __dir__ = reexport(__name__)
@@ -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__)