pythonx-compose 0.1.0a1__py3-none-any.whl → 0.1.0a3__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.
@@ -1 +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.
1
+ # GENERATED by scripts/gen_stubs.py from python-multiplatform's Kotlin-named stubs (artifact ks, workflow run 42402779166, commit 9507e6dd, Compose 1.11.1-ext.2). Do not edit.
@@ -1,33 +1,29 @@
1
1
  """The one rule that makes a `pythonx.compose.*` module a Pythonic view of a Kotlin package.
2
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.
3
+ The binder (`python-multiplatform`) serves every bound Kotlin package under its **Kotlin** name
4
+ (`androidx.compose.foundation.layout`), and since its #131 it also serves the Pythonic spellings
5
+ itself: snake_case names and keyword arguments, by this module's rule (`python_name`), with the
6
+ Kotlin spelling still working and `inspect.signature` reporting the snake_case keywords. It never
7
+ renames a namespace: `pythonx` is this package.
8
8
 
9
- This module is that layer. A mapped package's `__init__.py` is one line::
9
+ This module groups those Kotlin packages into `pythonx.compose.*`. A mapped package's `__init__.py`
10
+ is one line::
10
11
 
11
12
  __getattr__, __dir__ = reexport(__name__)
12
13
 
13
14
  and every name in it then resolves against the Kotlin package `pythonx-map.toml` maps it to:
14
15
 
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
16
+ - **Names.** A name that starts upper-case (a type, an object, a `@Composable`) keeps its Kotlin
17
+ spelling (`Modifier`, `Text`). Any other name is listed and reached by its snake_case spelling
17
18
  (`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`.
19
+ keeps its suffix (`padding__Dp`). The rule is applied forward to the module's real Kotlin names,
20
+ so a name the reverse rule could not invert still resolves.
21
+ - **Functions** are the binder's own callables; their keywords and signatures are the binder's.
24
22
  - **Value classes.** The manifest's `raw-primitive-allowed` list is handed to the binder once per
25
23
  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()`.
24
+ - **Methods on a proxy** are the binder's: since python-multiplatform #131 it serves
25
+ `Modifier.padding(16).fill_max_width()` and `m.padding(padding_values=...)` itself, by this
26
+ package's rule, with the Kotlin spelling still working. Nothing here takes part.
31
27
 
32
28
  - **Grouped constants.** Inside a Kotlin object served as a namespace, a type nested in it that
33
29
  is not one of its members groups the constants declared as exactly that type, so the notebook's
@@ -50,13 +46,14 @@ from __future__ import annotations
50
46
 
51
47
  import importlib
52
48
  import importlib.util
53
- import inspect
54
49
  import re
55
50
  import sys
56
51
  import types
57
52
  import weakref
53
+ from collections.abc import Callable, Iterable, Mapping
58
54
  from functools import lru_cache
59
55
  from pathlib import Path
56
+ from typing import Protocol, TypedDict, cast
60
57
 
61
58
  ROOT_MODULE = "python_multiplatform"
62
59
  BINDING_MODULE = "python_multiplatform.binding"
@@ -66,6 +63,30 @@ _MANIFEST = Path(__file__).resolve().parent / "pythonx-map.toml"
66
63
  _LOWER_UPPER = re.compile(r"([a-z0-9])([A-Z])")
67
64
  _ACRONYM_WORD = re.compile(r"([A-Z]+)([A-Z][a-z])")
68
65
 
66
+ # `pythonx-map.toml`'s shape. Its tables are keyed by Kotlin and module names, so the hyphenated
67
+ # keys are spelled in the functional form.
68
+ _ValueClasses = TypedDict("_ValueClasses", {"raw-primitive-allowed": list[str]}, total=False)
69
+ Manifest = TypedDict(
70
+ "Manifest",
71
+ {
72
+ "modules": dict[str, str],
73
+ "value-classes": _ValueClasses,
74
+ "aliases": dict[str, dict[str, "list[str] | dict[str, str]"]],
75
+ },
76
+ total=False,
77
+ )
78
+
79
+
80
+ class BindingLayer(Protocol):
81
+ """What this module asks of `python_multiplatform.binding`."""
82
+
83
+ def allow_raw_primitive(self, kotlin_type: str, /) -> None: ...
84
+ def register_empty(self, kotlin_type: object, empty_factory: object, /) -> None: ...
85
+
86
+
87
+ Describe = Callable[[types.ModuleType, str], object]
88
+ """`python_multiplatform.describe(module, name)`."""
89
+
69
90
 
70
91
  def snake_case(kotlin_name: str) -> str:
71
92
  """`fillMaxWidth` -> `fill_max_width`, `toURLString` -> `to_url_string`, `zIndex` -> `z_index`."""
@@ -85,12 +106,12 @@ def python_name(kotlin_name: str) -> str:
85
106
 
86
107
 
87
108
  @lru_cache(maxsize=1)
88
- def manifest() -> dict:
109
+ def manifest() -> Manifest:
89
110
  """`pythonx-map.toml`, parsed once."""
90
111
  import tomllib
91
112
 
92
113
  with _MANIFEST.open("rb") as handle:
93
- return tomllib.load(handle)
114
+ return cast(Manifest, tomllib.load(handle))
94
115
 
95
116
 
96
117
  def kotlin_package_of(module_name: str) -> str:
@@ -102,7 +123,7 @@ def kotlin_package_of(module_name: str) -> str:
102
123
  ) from None
103
124
 
104
125
 
105
- def binding_layer():
126
+ def binding_layer() -> BindingLayer:
106
127
  """The binder's installed binding module, or a `RuntimeError` that says what did not happen."""
107
128
  binding = sys.modules.get(BINDING_MODULE)
108
129
  if binding is None:
@@ -110,95 +131,47 @@ def binding_layer():
110
131
  "the python-multiplatform binding layer is not installed: the Kotlin host must run "
111
132
  "PythonxAdapter.install() before pythonx.compose can resolve a Kotlin declaration"
112
133
  )
113
- return binding
114
-
115
-
116
- _PRIMED = weakref.WeakSet()
117
-
134
+ return cast(BindingLayer, binding)
118
135
 
119
- _MEMBER_KEYWORDS: "weakref.WeakKeyDictionary" = weakref.WeakKeyDictionary()
120
136
 
137
+ _PRIMED: weakref.WeakSet[BindingLayer] = weakref.WeakSet()
138
+ _MODIFIER_TYPE = "androidx.compose.ui.Modifier"
121
139
 
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
140
 
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
141
+ def _modifier_companion() -> object:
142
+ """`Modifier.Companion` read live from `androidx.compose.ui.Modifier` (python-multiplatform #324)."""
143
+ return cast(object, importlib.import_module("androidx.compose.ui").Modifier.Companion)
175
144
 
176
145
 
177
- def _prime(binding) -> None:
178
- """Hand this binding layer the manifest's value-class allowlist and the member rule, once.
146
+ def _prime(binding: BindingLayer) -> None:
147
+ """Hand this binding layer the manifest's value-class allowlist and the default empty `Modifier`, once.
179
148
 
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.
149
+ Method names and keywords on a proxy are the binder's own since python-multiplatform #131
150
+ (snake_case by this package's rule); nothing is registered for them here. Since
151
+ python-multiplatform #324, `Modifier.Companion` is bound as a value on the proxy class and
152
+ `register_empty` accepts a callable (`SPEC.md` S6.2, #138); if the caller has not already
153
+ registered an explicit empty factory for `androidx.compose.ui.Modifier`, `_modifier_companion`
154
+ is registered as the default.
182
155
  """
183
156
  if binding in _PRIMED:
184
157
  return
185
158
  for kotlin_type in manifest().get("value-classes", {}).get("raw-primitive-allowed", ()):
186
159
  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)
160
+ factories = cast("Mapping[str, object]", getattr(binding, "_EMPTY_FACTORIES", {}))
161
+ if _MODIFIER_TYPE not in factories:
162
+ binding.register_empty(_MODIFIER_TYPE, _modifier_companion)
190
163
  _PRIMED.add(binding)
191
164
 
192
165
 
193
- def kotlin_module(module_name: str):
166
+ def kotlin_module(module_name: str) -> types.ModuleType:
194
167
  """The binder's module for the Kotlin package `module_name` maps to."""
195
168
  _prime(binding_layer())
196
169
  return importlib.import_module(kotlin_package_of(module_name))
197
170
 
198
171
 
199
- def _name_table(kotlin) -> dict:
172
+ def _name_table(kotlin: types.ModuleType) -> dict[str, str]:
200
173
  """Pythonic name -> Kotlin name for everything the Kotlin module reports, with collisions refused."""
201
- table = {}
174
+ table: dict[str, str] = {}
202
175
  for kotlin_name in dir(kotlin):
203
176
  if kotlin_name.startswith("_"):
204
177
  continue
@@ -213,7 +186,7 @@ def _name_table(kotlin) -> dict:
213
186
  return table
214
187
 
215
188
 
216
- def _declared_type(kotlin, kotlin_name: str):
189
+ def _declared_type(kotlin: types.ModuleType, kotlin_name: str) -> tuple[object, str | None] | None:
217
190
  """`(kind, declared return type)` of the bound name [kotlin_name] in [kotlin], without reading it.
218
191
 
219
192
  `python_multiplatform.describe(module, name)` answers from the binder's table and never runs a
@@ -221,7 +194,7 @@ def _declared_type(kotlin, kotlin_name: str):
221
194
  that is not a single bound declaration (an overload set, a child package), answers `None`.
222
195
  """
223
196
  root = sys.modules.get(ROOT_MODULE)
224
- describe = getattr(root, "describe", None)
197
+ describe = cast("Describe | None", getattr(root, "describe", None))
225
198
  if describe is None:
226
199
  return None
227
200
  try:
@@ -230,78 +203,23 @@ def _declared_type(kotlin, kotlin_name: str):
230
203
  return None
231
204
  if not isinstance(described, dict):
232
205
  return None
233
- return described.get("kind"), described.get("returns")
206
+ # The binder's answer is a plain dict of strings; its values are only compared and sliced here.
207
+ answer = cast(dict[str, object], described)
208
+ returns = answer.get("returns")
209
+ return answer.get("kind"), returns if isinstance(returns, str) else None
234
210
 
235
211
 
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):
212
+ def _pythonic(value: object) -> object:
295
213
  if isinstance(value, types.ModuleType):
296
214
  # A Kotlin object served as a sub-package. Since python-multiplatform #35 the binder lists it
297
215
  # 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)
216
+ return _kotlin_object(value)
217
+ # A function is the binder's own callable: since python-multiplatform #131 it takes snake_case
218
+ # keywords and reports them in `inspect.signature` itself.
219
+ return value
302
220
 
303
221
 
304
- def _resolve(kotlin, name: str):
222
+ def _resolve(kotlin: types.ModuleType, name: str) -> tuple[object, bool] | None:
305
223
  """What the Pythonic [name] means in the binder's module [kotlin]: `(value, cacheable)` or `None`.
306
224
 
307
225
  A name the module lists is looked up by the rule. An upper-case name it does not list may be a
@@ -311,23 +229,23 @@ def _resolve(kotlin, name: str):
311
229
  """
312
230
  kotlin_name = _name_table(kotlin).get(name)
313
231
  if kotlin_name is not None:
314
- value = getattr(kotlin, kotlin_name)
232
+ value = cast(object, getattr(kotlin, kotlin_name))
315
233
  # The binder declined to cache it -- a live property, read again on every access -- so this
316
234
  # layer does not freeze it either.
317
- return _pythonic(value, name), kotlin_name in vars(kotlin)
235
+ return _pythonic(value), kotlin_name in cast(Mapping[str, object], vars(kotlin))
318
236
  if name[:1].isupper():
319
237
  try:
320
238
  nested = importlib.import_module(kotlin.__name__ + "." + name)
321
239
  except ImportError:
322
240
  return None
323
- return KotlinObject(nested), True
241
+ return _kotlin_object(nested), True
324
242
  return None
325
243
 
326
244
 
327
245
  _STATIC_GETTER = "STATIC_GETTER"
328
246
 
329
247
 
330
- def _constant_groups(kotlin) -> dict:
248
+ def _constant_groups(kotlin: types.ModuleType) -> dict[str, dict[str, str]]:
331
249
  """Group name -> {Pythonic constant name: Kotlin name}, for the Kotlin object [kotlin].
332
250
 
333
251
  A group is a type nested in the object, `P.G`, that some constant of `P` is declared as; it holds
@@ -337,12 +255,13 @@ def _constant_groups(kotlin) -> dict:
337
255
  """
338
256
  prefix = kotlin.__name__ + "."
339
257
  members = _name_table(kotlin)
340
- groups: dict = {}
258
+ groups: dict[str, dict[str, str]] = {}
341
259
  for pythonic, kotlin_name in members.items():
342
260
  declared = _declared_type(kotlin, kotlin_name)
343
261
  if declared is None or declared[0] != _STATIC_GETTER or not declared[1]:
344
262
  continue
345
- group = declared[1][len(prefix):] if declared[1].startswith(prefix) else ""
263
+ returns = declared[1]
264
+ group = returns[len(prefix):] if returns.startswith(prefix) else ""
346
265
  if not group[:1].isupper() or "." in group or group in members:
347
266
  continue
348
267
  groups.setdefault(group, {})[pythonic] = kotlin_name
@@ -360,17 +279,24 @@ class KotlinObject:
360
279
 
361
280
  __slots__ = ("_kotlin", "_cache", "_groups")
362
281
 
363
- def __init__(self, kotlin_module):
282
+ _kotlin: types.ModuleType
283
+ _cache: dict[str, object]
284
+ _groups: dict[str, dict[str, str]] | None
285
+
286
+ def __init__(self, kotlin_module: types.ModuleType) -> None:
364
287
  object.__setattr__(self, "_kotlin", kotlin_module)
365
- object.__setattr__(self, "_cache", {})
288
+ object.__setattr__(self, "_cache", dict[str, object]())
366
289
  object.__setattr__(self, "_groups", None)
367
290
 
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
291
+ def _constant_groups(self) -> dict[str, dict[str, str]]:
292
+ known = self._groups
293
+ if known is not None:
294
+ return known
295
+ groups = _constant_groups(self._kotlin)
296
+ object.__setattr__(self, "_groups", groups)
297
+ return groups
372
298
 
373
- def __getattr__(self, name):
299
+ def __getattr__(self, name: str) -> object:
374
300
  if name.startswith("__") and name.endswith("__"):
375
301
  raise AttributeError(name)
376
302
  cached = self._cache.get(name)
@@ -384,22 +310,43 @@ class KotlinObject:
384
310
  f"{self._kotlin.__name__} has nothing spelled {name!r} under pythonx's naming rule"
385
311
  )
386
312
  # 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
313
+ group = ConstantGroup(self, f"{self._kotlin.__name__}.{name}", members)
314
+ self._cache[name] = group
315
+ return group
388
316
  value, cacheable = resolved
389
317
  if cacheable:
390
318
  self._cache[name] = value
391
319
  return value
392
320
 
393
- def __setattr__(self, name, value):
321
+ def __setattr__(self, name: str, value: object) -> None:
394
322
  raise AttributeError(f"{self._kotlin.__name__} is a Kotlin object; its members are read-only here")
395
323
 
396
- def __dir__(self):
324
+ def __dir__(self) -> list[str]:
397
325
  return sorted(set(_name_table(self._kotlin)) | set(self._constant_groups()))
398
326
 
399
- def __repr__(self):
327
+ def __repr__(self) -> str:
400
328
  return f"<pythonx view of Kotlin object {self._kotlin.__name__}>"
401
329
 
402
330
 
331
+ class CallableKotlinObject(KotlinObject):
332
+ """A Kotlin object whose name is also a function in its package (`Color`), callable as that function.
333
+
334
+ The binder makes such a module callable (python-multiplatform #78): `Color(0xFFFFFFFF)` calls
335
+ the `Color` function, while `Color.Red` is still a constant of the object. Calling goes to the
336
+ binder's module itself, which resolves the function on every call.
337
+ """
338
+
339
+ __slots__ = ()
340
+
341
+ def __call__(self, *args: object, **kwargs: object) -> object:
342
+ return cast("Callable[..., object]", self._kotlin)(*args, **kwargs)
343
+
344
+
345
+ def _kotlin_object(kotlin_module: types.ModuleType) -> KotlinObject:
346
+ """The namespace for a Kotlin object module: callable only when the binder made the module callable."""
347
+ return CallableKotlinObject(kotlin_module) if callable(kotlin_module) else KotlinObject(kotlin_module)
348
+
349
+
403
350
  class ConstantGroup:
404
351
  """The constants of a Kotlin object declared as one type nested in it: `Alignment.Horizontal`.
405
352
 
@@ -410,12 +357,16 @@ class ConstantGroup:
410
357
 
411
358
  __slots__ = ("_owner", "_kotlin_type", "_members")
412
359
 
413
- def __init__(self, owner, kotlin_type, members):
360
+ _owner: KotlinObject
361
+ _kotlin_type: str
362
+ _members: dict[str, str]
363
+
364
+ def __init__(self, owner: KotlinObject, kotlin_type: str, members: Mapping[str, str]) -> None:
414
365
  object.__setattr__(self, "_owner", owner)
415
366
  object.__setattr__(self, "_kotlin_type", kotlin_type)
416
367
  object.__setattr__(self, "_members", dict(members))
417
368
 
418
- def __getattr__(self, name):
369
+ def __getattr__(self, name: str) -> object:
419
370
  if name.startswith("__") and name.endswith("__"):
420
371
  raise AttributeError(name)
421
372
  if name not in self._members:
@@ -424,33 +375,36 @@ class ConstantGroup:
424
375
  f"{self._owner._kotlin.__name__} declared as {self._kotlin_type}: "
425
376
  + ", ".join(sorted(self._members))
426
377
  )
427
- return getattr(self._owner, name)
378
+ # The owner's own resolver, by name: a constant is never an attribute of `KotlinObject` itself.
379
+ return self._owner.__getattr__(name)
428
380
 
429
- def __setattr__(self, name, value):
381
+ def __setattr__(self, name: str, value: object) -> None:
430
382
  raise AttributeError(f"{self._kotlin_type} is a group of Kotlin constants; they are read-only here")
431
383
 
432
- def __dir__(self):
384
+ def __dir__(self) -> list[str]:
433
385
  return sorted(self._members)
434
386
 
435
- def __repr__(self):
387
+ def __repr__(self) -> str:
436
388
  return f"<pythonx group of {self._owner._kotlin.__name__} constants declared {self._kotlin_type}>"
437
389
 
438
390
 
439
- def _aliases(module_name: str) -> dict:
391
+ def _aliases(module_name: str) -> dict[str, tuple[str, str]]:
440
392
  """Name -> `(source module, attribute path in it)`, per the manifest's `[aliases]`.
441
393
 
442
394
  An entry under a source module is a list of names (lent under the same name, path = the name) or
443
395
  a table `{ lent = "Path.To.Attr" }` (a different name, through an attribute path).
444
396
  """
445
- borrowed = {}
397
+ borrowed: dict[str, tuple[str, str]] = {}
446
398
  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()
399
+ pairs: Iterable[tuple[str, str]] = (
400
+ ((name, name) for name in lent) if isinstance(lent, list) else lent.items()
401
+ )
448
402
  for name, path in pairs:
449
403
  borrowed[name] = (source, path)
450
404
  return borrowed
451
405
 
452
406
 
453
- def _borrow(module_name: str, name: str, source: str, path: str):
407
+ def _borrow(module_name: str, name: str, source: str, path: str) -> object:
454
408
  """The value of the alias [name]: [path] read in [source].
455
409
 
456
410
  A bare name is resolved once and kept in the owner's namespace. A path with attribute steps is
@@ -458,22 +412,22 @@ def _borrow(module_name: str, name: str, source: str, path: str):
458
412
  serves `Icons.Default` afresh on every read and this layer does not freeze it either.
459
413
  """
460
414
  head, *rest = path.split(".")
461
- value = getattr(importlib.import_module(source), head)
415
+ value = cast(object, getattr(importlib.import_module(source), head))
462
416
  for step in rest:
463
- value = getattr(value, step)
417
+ value = cast(object, getattr(value, step))
464
418
  if not rest:
465
419
  setattr(sys.modules[module_name], name, value)
466
420
  return value
467
421
 
468
422
 
469
- def reexport(module_name: str):
423
+ def reexport(module_name: str) -> tuple[Callable[[str], object], Callable[[], list[str]]]:
470
424
  """The module-level `__getattr__` and `__dir__` for the mapped package `module_name`.
471
425
 
472
426
  Nothing is resolved here, only when a name is first read, so importing a `pythonx.compose`
473
427
  module needs neither the manifest row's Kotlin package nor an installed binder.
474
428
  """
475
429
 
476
- def __getattr__(name):
430
+ def __getattr__(name: str) -> object:
477
431
  if name.startswith("__") and name.endswith("__"):
478
432
  raise AttributeError(name)
479
433
  if importlib.util.find_spec(module_name + "." + name) is not None:
@@ -501,8 +455,8 @@ def reexport(module_name: str):
501
455
  setattr(sys.modules[module_name], name, value)
502
456
  return value
503
457
 
504
- def __dir__():
505
- own = set(vars(sys.modules[module_name])) | set(_aliases(module_name))
458
+ def __dir__() -> list[str]:
459
+ own = set(cast(Mapping[str, object], vars(sys.modules[module_name]))) | set(_aliases(module_name))
506
460
  try:
507
461
  own.update(_name_table(kotlin_module(module_name)))
508
462
  except (RuntimeError, ImportError):
@@ -1,4 +1,4 @@
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.
1
+ # GENERATED by scripts/gen_stubs.py from python-multiplatform's Kotlin-named stubs (artifact ks, workflow run 42402779166, commit 9507e6dd, Compose 1.11.1-ext.2). Do not edit.
2
2
  import typing as _t
3
3
 
4
4
  class BorderStroke:
@@ -1,2 +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.
1
+ # GENERATED by scripts/gen_stubs.py from python-multiplatform's Kotlin-named stubs (artifact ks, workflow run 42402779166, commit 9507e6dd, Compose 1.11.1-ext.2). Do not edit.
2
2
  from pythonx.compose.layout import *
@@ -1,4 +1,4 @@
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.
1
+ # GENERATED by scripts/gen_stubs.py from python-multiplatform's Kotlin-named stubs (artifact ks, workflow run 42402779166, commit 9507e6dd, Compose 1.11.1-ext.2). Do not edit.
2
2
  import typing as _t
3
3
 
4
4
  class _TextFieldState_clearText(_t.Protocol):
@@ -25,18 +25,24 @@ class OutputTransformation:
25
25
  class TextFieldBuffer:
26
26
  """Kotlin: androidx.compose.foundation.text.input.TextFieldBuffer"""
27
27
 
28
+ class TextFieldDecorator:
29
+ """Kotlin: androidx.compose.foundation.text.input.TextFieldDecorator"""
30
+
28
31
  class TextFieldLineLimits:
29
32
  """Kotlin: androidx.compose.foundation.text.input.TextFieldLineLimits"""
30
33
 
31
34
  class TextFieldState:
32
35
  """Kotlin: androidx.compose.foundation.text.input.TextFieldState"""
33
- def __init__(self, initialText: str = ..., initialSelection: _t.Any = ...) -> None:
36
+ def __init__(self, initial_text: str = ..., initial_selection: _t.Any = ...) -> None:
34
37
  """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
38
  ...
36
39
  clear_text: _t.ClassVar[_TextFieldState_clearText]
37
40
  set_text_and_place_cursor_at_end: _t.ClassVar[_TextFieldState_setTextAndPlaceCursorAtEnd]
38
41
  set_text_and_select_all: _t.ClassVar[_TextFieldState_setTextAndSelectAll]
39
42
  to_text_field_buffer: _t.ClassVar[_TextFieldState_toTextFieldBuffer]
43
+ def edit(self, block: _t.Callable[[TextFieldBuffer], None]) -> None:
44
+ """Kotlin member: androidx.compose.foundation.text.input.TextFieldState.edit"""
45
+ ...
40
46
  @property
41
47
  def composition(self) -> _t.Any | None:
42
48
  """Kotlin property: androidx.compose.foundation.text.input.TextFieldState.composition (androidx.compose.foundation.text.input.TextFieldState.composition: androidx.compose.ui.text.TextRange?)"""