diff --git a/PROJECT.md b/PROJECT.md index 9358df9..4ed2a79 100644 --- a/PROJECT.md +++ b/PROJECT.md @@ -35,14 +35,14 @@ Compose(`androidx.compose.*`)를 파이썬에서 쓰기 위한 pip 패키지 **` ``` python3 -m pytest tests -q - PythonMultiplatform 체크아웃 없음 55 passed, 56 skipped - python-multiplatform develop ba4c6f49+ 111 passed + PythonMultiplatform 체크아웃 없음 53 passed, 56 skipped + python-multiplatform develop ba4c6f49+ 109 passed 그보다 오래된 체크아웃 같은 수, 그중 5 개 건너뜀 (member resolver 없음) ``` member resolver(python-multiplatform `ba4c6f49`)가 없는 바인더에서는 proxy 의 snake_case 메서드를 부르는 5 개가 건너뛴다. 건너뜀은 통과가 아니다. -체크아웃 없이 통과하는 55 개 중 다수는 *부재*(옛 토큰·삭제된 파일이 없음)를 확인하는 것이라 기능 진척으로 세지 +체크아웃 없이 통과하는 53 개 중 다수는 *부재*(옛 토큰·삭제된 파일이 없음)를 확인하는 것이라 기능 진척으로 세지 않는다. ### 마일스톤 @@ -70,7 +70,7 @@ pythonx/compose/ import 패키지 (현재 대부분 독스트링만 있 _reexport.py 매니페스트 모듈 전체에 적용되는 재노출 규칙 하나 ui/ modifier.py (빈 Modifier 등록 지점), alignment.py layout/ arrangement.py - material3/ icon.py, color_scheme.py (호출 불가 기록용), 빈 파일 28 개 + material3/ __init__.py 하나 (위젯은 모두 재노출 규칙으로) lite/ 2024 JPype 프로토타입 (은퇴, 바이너리 97 개 추적 중) native/ 서브모듈 → thisisthepy/swing-graalvm-demo tests/ pytest (unittest 스타일) diff --git a/docs/SPEC.md b/docs/SPEC.md index 3174022..65b092a 100644 --- a/docs/SPEC.md +++ b/docs/SPEC.md @@ -20,8 +20,8 @@ Run from a worktree with `python3 -m pytest tests -q` (pytest 8, CPython 3.13): | Environment | Result | |---|---| -| No `PythonMultiplatform` checkout found | **55 passed, 56 skipped**, 67 subtests passed | -| python-multiplatform `develop` at `ba4c6f49` or later (has `add_member_resolver`) | **111 passed**, 73 subtests passed | +| No `PythonMultiplatform` checkout found | **53 passed, 56 skipped**, 66 subtests passed | +| python-multiplatform `develop` at `ba4c6f49` or later (has `add_member_resolver`) | **109 passed**, 72 subtests passed | | an older checkout, without `add_member_resolver` | the same, with 5 of them skipped | Without a checkout, the 56 tests that install the binder's layers through `tests/adapter.py` skip. @@ -29,7 +29,7 @@ Against a binder older than `ba4c6f49`, the 5 that call a snake_case method on a returned skip, because that needs its member resolver (python-multiplatform #17). A skip is not a pass. -Of the 55 that pass without a checkout, most assert **absence** (a retired token, a deleted file, a docstring that +Of the 53 that pass without a checkout, most assert **absence** (a retired token, a deleted file, a docstring that exists). Those are listed in §9 and are not counted as features. --- @@ -163,13 +163,13 @@ toggle family, `TextField`, `Checkbox` and `Switch` are meant to be reached thro host binds no material3 declaration, so no test here exercises one; the render proof per widget is issue #9. -### S5.3 `Icon` and colour schemes — `partial` +### S5.3 `Icon` and colour schemes — `planned` -`pythonx/compose/material3/icon.py` and `color_scheme.py` import without raising -(`tests/test_material3_module.py::TheTwoUnreachableWrappersLoadWithoutCrashing`) but are **not -callable**: `Icon` needs an `ImageBitmap` / `ImageVector` / `Painter` nothing bound can produce, and -the two colour-scheme factories take 36 `Color` parameters. The notebook uses `Icon` and -`DefaultIcons` (§8). +`Icon` needs an `ImageBitmap` / `ImageVector` / `Painter`, and nothing bound produces one until the +binder walks `material-icons-core` (python-multiplatform #37; then `DefaultIcons` is +`Icons.Default`, INTENT §5.7). The two colour-scheme factories take 36 `Color` parameters against +the binding's omission cap (python-multiplatform `a6742a1c`). The hand-written `icon.py` / +`color_scheme.py` that recorded this were dead code and are deleted (#31). ## 6. Modifiers — extension functions as methods @@ -258,9 +258,11 @@ name)` (python-multiplatform #36; issue #9). These tests pass and assert that retired 2024 mechanisms are gone. They are not features: -- `tests/test_legacy_modules.py` (7 tests): no chaquopy / JPype tokens or dead relative imports in - `layout/__init__.py`, `lite/*.py`, `test/main.py`, `ui/unit/__init__.py`, `wrapper/__init__.py`, +- `tests/test_legacy_modules.py` (6 tests): no chaquopy / JPype tokens or dead relative imports in + `layout/__init__.py`, `lite/*.py`, `test/main.py`, `ui/unit/__init__.py`, `material3/__init__.py`; each retired module carries a real docstring. +- `tests/test_material3_module.py::TheDeadFilesAreGone` (2 tests): `material3/` holds only its + `__init__.py`, and `wrapper/` is gone (#31). - `tests/test_ui_init_module.py::TheChaquopyUiInitMechanismIsGone` (1 test). --- @@ -282,15 +284,9 @@ Found in the repository; not covered by `docs/INTENT.md`, or in conflict with it 4. **Two spellings for one module.** The manifest maps both `pythonx.compose.layout` and `pythonx.compose.foundation.layout` (plus `pythonx.compose.foundation` and `pythonx.compose`) "while the spelling settles". -5. **A removed mechanism kept as a record.** `material3/icon.py` and `color_scheme.py` still import - `androidx.compose.material3.IconKt` and scan for mangled JVM names (`"Icon-"`), the reflection - approach INTENT §3 excludes. - The package no longer imports them: star-importing them made `import pythonx.compose.material3` - fail wherever `androidx.compose.material3` was not bound, and shadowed the binder's `Icon`. - Whether the files stay is still open. +5. *(Resolved, #31.)* `material3/icon.py` and `color_scheme.py`, dead reflection code, are deleted. 6. *(Decided, INTENT §5.3.)* Grouped alignment constants: both spellings are to be served; the grouped one is not built yet (§7). -7. **28 empty `material3/*.py` files** (`checkbox.py`, `switch.py`, `scaffold.py`, ...), and the - submodule `pythonx/compose/native` → `thisisthepy/swing-graalvm-demo`, which INTENT does not - mention. +7. The submodule `pythonx/compose/native` → `thisisthepy/swing-graalvm-demo`, which INTENT does not + mention. (The 29 empty `material3/*.py` files are deleted, #31.) 8. **The `test/` directory** — a 2023–2024 Kotlin Multiplatform sample (`pycomposeui`, chaquopy era). Kept as-is pending the maintainer's decision (INTENT §4.2). diff --git a/docs/guide/getting-started.html b/docs/guide/getting-started.html index b980a55..33fb8f5 100644 --- a/docs/guide/getting-started.html +++ b/docs/guide/getting-started.html @@ -43,7 +43,7 @@

1. Get the source1. 소스

2. Run the tests2. 테스트 실행

python3 -m pytest tests -q
-

Expect 55 passed, 56 skipped. The skipped tests drive the binder's adaptation layer and need a python-multiplatform checkout beside this one, or:

55 passed, 56 skipped 가 정상입니다. 건너뛴 테스트는 바인더의 적응 계층을 구동하며, 옆에 python-multiplatform 체크아웃이 있거나 다음이 필요합니다:

+

Expect 53 passed, 56 skipped. The skipped tests drive the binder's adaptation layer and need a python-multiplatform checkout beside this one, or:

53 passed, 56 skipped 가 정상입니다. 건너뛴 테스트는 바인더의 적응 계층을 구동하며, 옆에 python-multiplatform 체크아웃이 있거나 다음이 필요합니다:

PYTHONMULTIPLATFORM_HOME=/path/to/python-multiplatform python3 -m pytest tests -q

With a current one (ba4c6f49 or later) all pass; an older one skips 5 that call a snake_case method on a proxy the binder returned. That is expected and recorded.

최신 체크아웃(ba4c6f49 이후)이면 전부 통과하고, 오래된 체크아웃은 바인더가 돌려준 proxy 의 snake_case 메서드를 부르는 5 개를 건너뜁니다. 예상된 상태이며 기록되어 있습니다.

diff --git a/docs/guide/status.html b/docs/guide/status.html index 4e416d5..dfee6f9 100644 --- a/docs/guide/status.html +++ b/docs/guide/status.html @@ -36,7 +36,7 @@ State상태Item항목 implemented구현Mapping manifest pythonx-map.toml매핑 매니페스트 pythonx-map.tomlimplemented구현@Composable identity decorator@Composable 항등 데코레이터partial부분Distribution as pythonx-compose — configured; no stubs yet, manifest outside the package directorypythonx-compose 배포 — 설정됨; 스텁 없음, 매니페스트가 패키지 디렉터리 밖implemented구현Modifier chains, overload dispatch, Dp as a numberModifier 체인, 오버로드 디스패치, 숫자 Dppartial부분Snake-case names, keywords and signatures — done for modules and method names; method keywords are still Kotlin'ssnake_case 이름·키워드·시그니처 — 모듈과 메서드 이름 완료, 메서드 키워드는 아직 Kotlin 이름partial부분Material 3 widgets — re-exported by rule; render proofs per widget pending (#9)Material 3 위젯 — 규칙으로 재노출됨; 위젯별 렌더 증거 대기(#9)partial부분Icon, colour schemes — importable, not callableIcon, 색 스킴 — import 가능, 호출 불가partial부분Alignment / Arrangement constants — reached from pythonx; grouped spelling pendingAlignment / Arrangement 상수 — pythonx 에서 읽힘; 묶음 표기 대기implemented구현pythonx as a real on-disk package re-exporting androidx.compose.* by one ruleandroidx.compose.* 를 규칙 하나로 재노출하는 실제 디스크 패키지 pythonxplanned계획.pyi stubs in the wheelwheel 안의 .pyi 스텁planned계획remember_saveable, DefaultIcons, coroutine scopesremember_saveable, DefaultIcons, 코루틴 스코프 -

Test baseline: 55 passed, 56 skipped without a python-multiplatform checkout; 111 passed with a current one.

테스트 기준선: python-multiplatform 체크아웃이 없으면 55 passed, 56 skipped, 최신 체크아웃이면 111 passed.

+

Test baseline: 53 passed, 56 skipped without a python-multiplatform checkout; 109 passed with a current one.

테스트 기준선: python-multiplatform 체크아웃이 없으면 53 passed, 56 skipped, 최신 체크아웃이면 109 passed.

FAQ

diff --git a/pythonx/compose/material3/__init__.py b/pythonx/compose/material3/__init__.py index 5c43820..2973b78 100644 --- a/pythonx/compose/material3/__init__.py +++ b/pythonx/compose/material3/__init__.py @@ -1,83 +1,23 @@ -# `icon.py` and `color_scheme.py` are records, not part of this package's surface, and are not -# imported here. Star-importing them made `import pythonx.compose.material3` import -# `androidx.compose.material3` at load time -- failing wherever that Kotlin package is not bound -- -# and put their dead `Icon` / `ColorScheme` classes in front of the binder's real ones, which the -# re-export rule below serves. Whether the two files stay is an open question (SPEC, Outside intent 5). +"""`pythonx.compose.material3` -- the Pythonic view of `androidx.compose.material3`. +Every Material 3 composable (`Text`, `Button`, `Card`, `TextField`, `Checkbox`, ...) is served by +the one rule in `pythonx/compose/_reexport.py`: Kotlin's own declaration, upper-case names kept, +keyword arguments in snake_case (`on_click=`). There is no per-widget file here, on purpose +(AGENTS.md section 13). The notebook's `Column`, `Row` and `Spacer` are answered from +`pythonx.compose.layout` by the manifest's `[aliases]` (INTENT section 5.2). -# `Text` is not here. `pythonx.compose.material3.Text` is a walked declaration -- the adaptation -# layer builds it from the artefact table, the same way `pythonx.compose.ui.Modifier.padding` is -# built and not hand-written (`pythonx/compose/ui/modifier.py`). A per-declaration `text.py` used to -# be imported here; it duplicated what the layer already does and could not be repaired (it looked -# `Text-` up by a name-prefix search Kotlin's value-class mangling makes impossible -- see -# `modifier.py`'s module docstring for why). `tests/test_material3_module.py` is what checks this. -# -# `Button`, `Card`, `ListItem`, `Badge`/`BadgedBox` and `MaterialTheme` are not here either, for the -# same reason and on the same evidence shape: `PythonMultiplatform` commit `a6742a1c` -# (`ComposableRenderTest.kt`) renders each of them from Python with no wrapper at all, content -# lambda and (where present) `on_click` included, against the real `material3` jar -- -# -# buttonComposesItsClickHandlerAndItsRowScopedContent -> buttons.py (Button et al.) -# cardResolvesToTheNonClickableOverloadAndDrawsItsContent -> cards.py (Card et al.) -# listItemComposesItsHeadlineContentUnderItsSnakeCasedName -> lists.py (ListItem) -# badgeDrawsItsLeafFormWithNoArgumentsAndMoreWithContent -> badge.py (Badge) -# badgedBoxComposesBothItsBadgeAndItsContentScopes -> badge.py (BadgedBox) -# materialThemeComposesAZeroArgumentContentLambda -> material_theming.py (MaterialTheme) -# -# Each of those files was a copy of the same `__COMPILED_CODE__` / `find_composable` / -# `name.startswith("-")` template `text.py` and `modifier.py` used -- the sibling declarations -# in each file (`ElevatedButton`, `OutlinedCard`, ...) were never individually rendered, but they are -# the identical template with only the `*Kt` attribute name changed, so the one proof per file is the -# same class of evidence the `Text` deletion already established: the template is dead, not the -# specific name. `tests/test_material3_module.py` asserts each file is gone. -# -# `icon_button.py` is now gone entirely, not just half. `IconButton` and its plain siblings already -# had the render proof (`iconButtonComposesItsClickHandlerAndItsContent`, `a6742a1c`). The -# `IconToggleButton` family (`checked: Boolean`, `onCheckedChange: (Boolean) -> Unit`) needed a -# different shape of proof -- a static render delivers no events, so nothing about a callback slot -# could be shown by rasterising -- and `PythonMultiplatform` commit `3fde8bd6` -# (`CallbackDrivenRenderTest.kt`) supplies it: a real pointer press and release through -# `ImageComposeScene.sendPointerEvent`, asserted to invoke the Python callback with the value Compose -# handed it, on **two** declarations deliberately (`Checkbox` in `CheckboxKt`, then `Switch` in -# `SwitchKt`, specifically to rule out a mechanism that only happens to work for one file). The -# `IconToggleButton` family is a third declaration, in a third file (`IconButtonKt`), with that exact -# same `(Boolean) -> Unit` shape -- the same class of generalisation this module already applies -# within a single file for `Button`'s and `IconButton`'s untested siblings, extended here across -# files because the render test itself was built to cross that boundary. -# -# `text_field.py` is now gone too. `on_value_change` is `(String) -> Unit` -- a shape -# `CallbackDrivenRenderTest.kt` did not used to drive, only `(Boolean) -> Unit` -- so it stayed -# hand-written after `icon_button.py` went. `PythonMultiplatform` commit `cac8243f` closes that gap: -# `typingIntoATextFieldInvokesThePythonCallbackWithTheStringAndTheNextRenderShowsIt` types a real key -# event into a `pythonx`-bound `TextField`, asserts the Python callback received the string, and -# asserts a fresh render shows it; `twoKeystrokesAccumulateAcrossTwoFreshScenes` repeats it across two -# scenes to show the string accumulates rather than just landing once. Same bar the toggle buttons -# met, same conclusion. `tests/test_material3_module.py` is what checks all of this. -# -# `icon.py` and `color_scheme.py` do **not** get the same conclusion, and are not going anywhere: -# `PythonMultiplatform` commit `a6742a1c` pinned `Icon`, `lightColorScheme` and `darkColorScheme` as -# *unreachable* through the walked table -- `Icon` needs an `ImageBitmap`/`ImageVector`/`Painter` -# nothing walked can produce, and the two color-scheme factories need 36 `Color` parameters against a -# 6-parameter omission cap -- which is a stronger, opposite finding from the one that deleted -# `text.py`/`icon_button.py`/`text_field.py` (those were proven *redundant*; these were proven -# *uncallable any way at all*). The hand-written wrapper is the only Python-facing record of that, so -# it stays. What it no longer does is subclass `pythonx.compose.runtime.Composable`: `7d6c0a1` -# replaced that name's 2024 *class* with a plain identity-decorator *function*, and a function cannot -# be a base class -- `class Icon(Composable):` raised `TypeError` the moment this package was -# imported, which broke every other, working declaration in this package along with it, not just the -# two that are genuinely unreachable. Both classes are now defined plain, without a base, so importing -# this package no longer depends on two declarations that can never be called anyway. Calling either -# still fails (`self.composer` is never set -- nothing sets it, because the composer-threading base -# class it depended on is exactly what `7d6c0a1` retired), which is accurate: neither declaration -# works, before or after this fix. `tests/test_material3_module.py` checks the import no longer -# raises. -# -# `checkbox.py` and `switch.py` are not files in the sense any of the above are: both have been -# empty since before this package had an adaptation layer to defer to (`git log -p` on either shows -# no non-empty version). `Checkbox`/`Switch` reach Python entirely through the walked table -- which -# is exactly what `CallbackDrivenRenderTest.kt` exercises above -- and the empty files cost nothing -# and assert nothing; they are left as they are rather than deleted, so as not to conflate "always -# empty" with "emptied on evidence" in the file history. +## What used to be here + +Per-widget wrappers from 2024 (`text.py`, `buttons.py`, `cards.py`, `icon_button.py`, +`text_field.py`, ...), each a copy of one template that looked a Kotlin function up by a +mangled-name prefix (`name.startswith("Button-")`), which Kotlin's value-class mangling makes +impossible. They were deleted as the binder rendered each declaration with no wrapper +(python-multiplatform `c60bcfeb`, `a6742a1c`, `3fde8bd6`, `cac8243f`). `icon.py` and +`color_scheme.py` were kept as records of two declarations that cannot be called yet, and 29 +sibling files had always been empty; all were deleted in pythonx-compose #31. What `icon.py` +recorded -- `Icon` needs an `ImageVector` nothing bound produces -- is SPEC S5.3. `git log` on this +directory has the rest. +""" from pythonx.compose._reexport import reexport diff --git a/pythonx/compose/material3/bottom_app_bar.py b/pythonx/compose/material3/bottom_app_bar.py deleted file mode 100644 index e69de29..0000000 diff --git a/pythonx/compose/material3/bottom_sheet.py b/pythonx/compose/material3/bottom_sheet.py deleted file mode 100644 index e69de29..0000000 diff --git a/pythonx/compose/material3/checkbox.py b/pythonx/compose/material3/checkbox.py deleted file mode 100644 index e69de29..0000000 diff --git a/pythonx/compose/material3/color_scheme.py b/pythonx/compose/material3/color_scheme.py deleted file mode 100644 index df7b962..0000000 --- a/pythonx/compose/material3/color_scheme.py +++ /dev/null @@ -1,361 +0,0 @@ -from androidx.compose.material3 import ColorSchemeKt -from androidx.compose.material3 import ColorScheme - -__COMPILED_CODE__ = None -Unit = None - -class ColorScheme: - """ M3 color scheme. - - Not usable -- kept only because `PythonMultiplatform` commit `a6742a1c` pinned `ColorScheme`'s - factories as unreachable through the walked adaptation table (36 `Color` parameters, all - defaulted in Kotlin, none omittable in the binding, against a 6-parameter omission cap), so this - hand-written wrapper is the only Python-facing record of that. It does not subclass - `pythonx.compose.runtime.Composable` (`7d6c0a1` made that name a plain identity-decorator - function, not a base class -- inheriting from it raises `TypeError` at class-definition time, - which used to break importing this whole package, not just this declaration). `compose()` below - still reaches for `self.composer`, which nothing sets; calling it fails, which is correct, since - the declaration cannot be reached this way either. - """ - - def __init__(self): - super().__init__() - self.__kotlin_composable = None - - def find_composable(self): - super().__init__() - - if self.__kotlin_composable is None: - try: - self.__kotlin_composable = ColorScheme - except Exception: - for name, obj in ColorSchemeKt.__dict__.items(): - if name.startswith("ColorScheme-"): - self.__kotlin_composable = obj - break - - if not self.__kotlin_composable: - raise Exception("ColorScheme is cannot be found") - - def compose( - self, - primary=__COMPILED_CODE__, - on_primary=__COMPILED_CODE__, - primary_container=__COMPILED_CODE__, - on_primary_container=__COMPILED_CODE__, - inverse_primary=__COMPILED_CODE__, - secondary=__COMPILED_CODE__, - on_secondary=__COMPILED_CODE__, - secondary_container=__COMPILED_CODE__, - on_secondary_container=__COMPILED_CODE__, - tertiary=__COMPILED_CODE__, - on_tertiary=__COMPILED_CODE__, - tertiary_container=__COMPILED_CODE__, - on_tertiary_container=__COMPILED_CODE__, - background=__COMPILED_CODE__, - on_background=__COMPILED_CODE__, - surface=__COMPILED_CODE__, - on_surface=__COMPILED_CODE__, - surface_variant=__COMPILED_CODE__, - on_surface_variant=__COMPILED_CODE__, - surface_tint=__COMPILED_CODE__, - inverse_surface=__COMPILED_CODE__, - inverse_on_surface=__COMPILED_CODE__, - error=__COMPILED_CODE__, - on_error=__COMPILED_CODE__, - error_container=__COMPILED_CODE__, - on_error_container=__COMPILED_CODE__, - outline=__COMPILED_CODE__, - outline_variant=__COMPILED_CODE__, - scrim=__COMPILED_CODE__, - surface_bright=__COMPILED_CODE__, - surface_dim=__COMPILED_CODE__, - surface_container=__COMPILED_CODE__, - surface_container_high=__COMPILED_CODE__, - surface_container_highest=__COMPILED_CODE__, - surface_container_low=__COMPILED_CODE__, - surface_container_lowest=__COMPILED_CODE__, - ): - kwargs = { key: value for key, value in dict( - primary=primary, - onPrimary=on_primary, - primaryContainer=primary_container, - onPrimaryContainer=on_primary_container, - inversePrimary=inverse_primary, - secondary=secondary, - onSecondary=on_secondary, - secondaryContainer=secondary_container, - onSecondaryContainer=on_secondary_container, - tertiary=tertiary, - onTertiary=on_tertiary, - tertiaryContainer=tertiary_container, - onTertiaryContainer=on_tertiary_container, - background=background, - onBackground=on_background, - surface=surface, - onSurface=on_surface, - surfaceVariant=surface_variant, - onSurfaceVariant=on_surface_variant, - surfaceTint=surface_tint, - inverseSurface=inverse_surface, - inverseOnSurface=inverse_on_surface, - error=error, - onError=on_error, - errorContainer=error_container, - onErrorContainer=on_error_container, - outline=outline, - outlineVariant=outline_variant, - scrim=scrim, - surfaceBright=surface_bright, - surfaceDim=surface_dim, - surfaceContainer=surface_container, - surfaceContainerHigh=surface_container_high, - surfaceContainerHighest=surface_container_highest, - surfaceContainerLow=surface_container_low, - surfaceContainerLowest=surface_container_lowest - - - ) if value is not __COMPILED_CODE__ } - - if self.__kotlin_composable is None: - self.find_composable() - - self.__kotlin_composable( - **kwargs, - c=self.composer, - changed=1 - ) - - -class lightColorScheme: - """ M3 light color scheme. - - Not usable -- see `ColorScheme`'s docstring above: `a6742a1c` pinned this factory unreachable - (36 `Color` parameters against a 6-parameter omission cap), and this no longer subclasses - `pythonx.compose.runtime.Composable` for the same class-definition-time `TypeError` reason. - """ - - def __init__(self): - super().__init__() - self.__kotlin_composable = None - - def find_composable(self): - super().__init__() - - if self.__kotlin_composable is None: - try: - self.__kotlin_composable = ColorSchemeKt.lightColorScheme - except Exception: - for name, obj in ColorSchemeKt.__dict__.items(): - if name.startswith("lightColorScheme-"): - self.__kotlin_composable = obj - break - - if not self.__kotlin_composable: - raise Exception("lightColorScheme is cannot be found") - - def compose( - self, - primary=__COMPILED_CODE__, - on_primary=__COMPILED_CODE__, - primary_container=__COMPILED_CODE__, - on_primary_container=__COMPILED_CODE__, - inverse_primary=__COMPILED_CODE__, - secondary=__COMPILED_CODE__, - on_secondary=__COMPILED_CODE__, - secondary_container=__COMPILED_CODE__, - on_secondary_container=__COMPILED_CODE__, - tertiary=__COMPILED_CODE__, - on_tertiary=__COMPILED_CODE__, - tertiary_container=__COMPILED_CODE__, - on_tertiary_container=__COMPILED_CODE__, - background=__COMPILED_CODE__, - on_background=__COMPILED_CODE__, - surface=__COMPILED_CODE__, - on_surface=__COMPILED_CODE__, - surface_variant=__COMPILED_CODE__, - on_surface_variant=__COMPILED_CODE__, - surface_tint=__COMPILED_CODE__, - inverse_surface=__COMPILED_CODE__, - inverse_on_surface=__COMPILED_CODE__, - error=__COMPILED_CODE__, - on_error=__COMPILED_CODE__, - error_container=__COMPILED_CODE__, - on_error_container=__COMPILED_CODE__, - outline=__COMPILED_CODE__, - outline_variant=__COMPILED_CODE__, - scrim=__COMPILED_CODE__, - surface_bright=__COMPILED_CODE__, - surface_dim=__COMPILED_CODE__, - surface_container=__COMPILED_CODE__, - surface_container_high=__COMPILED_CODE__, - surface_container_highest=__COMPILED_CODE__, - surface_container_low=__COMPILED_CODE__, - surface_container_lowest=__COMPILED_CODE__, - ): - kwargs = { key: value for key, value in dict( - primary=primary, - onPrimary=on_primary, - primaryContainer=primary_container, - onPrimaryContainer=on_primary_container, - inversePrimary=inverse_primary, - secondary=secondary, - onSecondary=on_secondary, - secondaryContainer=secondary_container, - onSecondaryContainer=on_secondary_container, - tertiary=tertiary, - onTertiary=on_tertiary, - tertiaryContainer=tertiary_container, - onTertiaryContainer=on_tertiary_container, - background=background, - onBackground=on_background, - surface=surface, - onSurface=on_surface, - surfaceVariant=surface_variant, - onSurfaceVariant=on_surface_variant, - surfaceTint=surface_tint, - inverseSurface=inverse_surface, - inverseOnSurface=inverse_on_surface, - error=error, - onError=on_error, - errorContainer=error_container, - onErrorContainer=on_error_container, - outline=outline, - outlineVariant=outline_variant, - scrim=scrim, - surfaceBright=surface_bright, - surfaceDim=surface_dim, - surfaceContainer=surface_container, - surfaceContainerHigh=surface_container_high, - surfaceContainerHighest=surface_container_highest, - surfaceContainerLow=surface_container_low, - surfaceContainerLowest=surface_container_lowest - - - ) if value is not __COMPILED_CODE__ } - - if self.__kotlin_composable is None: - self.find_composable() - - self.__kotlin_composable( - **kwargs, - c=self.composer, - changed=1 - ) - - -class darkColorScheme: - """ M3 dark color scheme. - - Not usable -- see `ColorScheme`'s docstring above: `a6742a1c` pinned this factory unreachable - (36 `Color` parameters against a 6-parameter omission cap), and this no longer subclasses - `pythonx.compose.runtime.Composable` for the same class-definition-time `TypeError` reason. - """ - - def __init__(self): - super().__init__() - self.__kotlin_composable = None - - def find_composable(self): - super().__init__() - - if self.__kotlin_composable is None: - try: - self.__kotlin_composable = ColorSchemeKt.darkColorScheme - except Exception: - for name, obj in ColorSchemeKt.__dict__.items(): - if name.startswith("darkColorScheme-"): - self.__kotlin_composable = obj - break - - if not self.__kotlin_composable: - raise Exception("darkColorScheme is cannot be found") - - def compose( - self, - primary=__COMPILED_CODE__, - on_primary=__COMPILED_CODE__, - primary_container=__COMPILED_CODE__, - on_primary_container=__COMPILED_CODE__, - inverse_primary=__COMPILED_CODE__, - secondary=__COMPILED_CODE__, - on_secondary=__COMPILED_CODE__, - secondary_container=__COMPILED_CODE__, - on_secondary_container=__COMPILED_CODE__, - tertiary=__COMPILED_CODE__, - on_tertiary=__COMPILED_CODE__, - tertiary_container=__COMPILED_CODE__, - on_tertiary_container=__COMPILED_CODE__, - background=__COMPILED_CODE__, - on_background=__COMPILED_CODE__, - surface=__COMPILED_CODE__, - on_surface=__COMPILED_CODE__, - surface_variant=__COMPILED_CODE__, - on_surface_variant=__COMPILED_CODE__, - surface_tint=__COMPILED_CODE__, - inverse_surface=__COMPILED_CODE__, - inverse_on_surface=__COMPILED_CODE__, - error=__COMPILED_CODE__, - on_error=__COMPILED_CODE__, - error_container=__COMPILED_CODE__, - on_error_container=__COMPILED_CODE__, - outline=__COMPILED_CODE__, - outline_variant=__COMPILED_CODE__, - scrim=__COMPILED_CODE__, - surface_bright=__COMPILED_CODE__, - surface_dim=__COMPILED_CODE__, - surface_container=__COMPILED_CODE__, - surface_container_high=__COMPILED_CODE__, - surface_container_highest=__COMPILED_CODE__, - surface_container_low=__COMPILED_CODE__, - surface_container_lowest=__COMPILED_CODE__, - ): - kwargs = { key: value for key, value in dict( - primary=primary, - onPrimary=on_primary, - primaryContainer=primary_container, - onPrimaryContainer=on_primary_container, - inversePrimary=inverse_primary, - secondary=secondary, - onSecondary=on_secondary, - secondaryContainer=secondary_container, - onSecondaryContainer=on_secondary_container, - tertiary=tertiary, - onTertiary=on_tertiary, - tertiaryContainer=tertiary_container, - onTertiaryContainer=on_tertiary_container, - background=background, - onBackground=on_background, - surface=surface, - onSurface=on_surface, - surfaceVariant=surface_variant, - onSurfaceVariant=on_surface_variant, - surfaceTint=surface_tint, - inverseSurface=inverse_surface, - inverseOnSurface=inverse_on_surface, - error=error, - onError=on_error, - errorContainer=error_container, - onErrorContainer=on_error_container, - outline=outline, - outlineVariant=outline_variant, - scrim=scrim, - surfaceBright=surface_bright, - surfaceDim=surface_dim, - surfaceContainer=surface_container, - surfaceContainerHigh=surface_container_high, - surfaceContainerHighest=surface_container_highest, - surfaceContainerLow=surface_container_low, - surfaceContainerLowest=surface_container_lowest - - - ) if value is not __COMPILED_CODE__ } - - if self.__kotlin_composable is None: - self.find_composable() - - self.__kotlin_composable( - **kwargs, - c=self.composer, - changed=1 - ) \ No newline at end of file diff --git a/pythonx/compose/material3/date_picker.py b/pythonx/compose/material3/date_picker.py deleted file mode 100644 index e69de29..0000000 diff --git a/pythonx/compose/material3/dialogs.py b/pythonx/compose/material3/dialogs.py deleted file mode 100644 index e69de29..0000000 diff --git a/pythonx/compose/material3/dividers.py b/pythonx/compose/material3/dividers.py deleted file mode 100644 index e69de29..0000000 diff --git a/pythonx/compose/material3/dynamic_color.py b/pythonx/compose/material3/dynamic_color.py deleted file mode 100644 index e69de29..0000000 diff --git a/pythonx/compose/material3/extended_fab.py b/pythonx/compose/material3/extended_fab.py deleted file mode 100644 index e69de29..0000000 diff --git a/pythonx/compose/material3/fab.py b/pythonx/compose/material3/fab.py deleted file mode 100644 index e69de29..0000000 diff --git a/pythonx/compose/material3/icon.py b/pythonx/compose/material3/icon.py deleted file mode 100644 index 47125c7..0000000 --- a/pythonx/compose/material3/icon.py +++ /dev/null @@ -1,61 +0,0 @@ -from androidx.compose.material3 import IconKt - -__COMPILED_CODE__ = None -Unit = None - -class Icon: - """ M3 icon. - - Not usable -- kept only because `PythonMultiplatform` commit `a6742a1c` pinned `Icon` as - unreachable through the walked adaptation table (every overload needs an `ImageBitmap`, - `ImageVector` or `Painter`, and nothing walked produces one), so this hand-written wrapper is the - only Python-facing record of that. It does not subclass `pythonx.compose.runtime.Composable` - (`7d6c0a1` made that name a plain identity-decorator function, not a base class -- inheriting from - it raises `TypeError` at class-definition time, which used to break importing this whole package, - not just this declaration). `compose()` below still reaches for `self.composer`, which nothing - sets; calling it fails, which is correct, since the declaration cannot be reached this way either. - """ - - def __init__(self): - super().__init__() - self.__kotlin_composable = None - - def find_composable(self): - super().__init__() - - if self.__kotlin_composable is None: - try: - self.__kotlin_composable = IconKt.Icon - except Exception: - for name, obj in IconKt.__dict__.items(): - if name.startswith("Icon-"): - self.__kotlin_composable = obj - break - - if not self.__kotlin_composable: - raise Exception("Icon is cannot be found") - - def compose( - self, - bitmap=__COMPILED_CODE__, - content_description=__COMPILED_CODE__, - modifier=__COMPILED_CODE__, - tint=__COMPILED_CODE__, - content=__COMPILED_CODE__ - ): - kwargs = { key: value for key, value in dict( - bitmap=bitmap, - contentDescription=content_description, - modifier=modifier, - tint=tint, - content=content - ) if value is not __COMPILED_CODE__ } - - if self.__kotlin_composable is None: - self.find_composable() - - self.__kotlin_composable( - **kwargs, - c=self.composer, - changed=1 - ) \ No newline at end of file diff --git a/pythonx/compose/material3/menus.py b/pythonx/compose/material3/menus.py deleted file mode 100644 index e69de29..0000000 diff --git a/pythonx/compose/material3/navigation_bar.py b/pythonx/compose/material3/navigation_bar.py deleted file mode 100644 index e69de29..0000000 diff --git a/pythonx/compose/material3/navigation_drawer.py b/pythonx/compose/material3/navigation_drawer.py deleted file mode 100644 index e69de29..0000000 diff --git a/pythonx/compose/material3/navigation_rail.py b/pythonx/compose/material3/navigation_rail.py deleted file mode 100644 index e69de29..0000000 diff --git a/pythonx/compose/material3/progress_indicators.py b/pythonx/compose/material3/progress_indicators.py deleted file mode 100644 index e69de29..0000000 diff --git a/pythonx/compose/material3/radio_button.py b/pythonx/compose/material3/radio_button.py deleted file mode 100644 index e69de29..0000000 diff --git a/pythonx/compose/material3/scaffold.py b/pythonx/compose/material3/scaffold.py deleted file mode 100644 index e69de29..0000000 diff --git a/pythonx/compose/material3/search_bar.py b/pythonx/compose/material3/search_bar.py deleted file mode 100644 index e69de29..0000000 diff --git a/pythonx/compose/material3/segmented_button.py b/pythonx/compose/material3/segmented_button.py deleted file mode 100644 index e69de29..0000000 diff --git a/pythonx/compose/material3/shape.py b/pythonx/compose/material3/shape.py deleted file mode 100644 index e69de29..0000000 diff --git a/pythonx/compose/material3/sliders.py b/pythonx/compose/material3/sliders.py deleted file mode 100644 index e69de29..0000000 diff --git a/pythonx/compose/material3/snackbars.py b/pythonx/compose/material3/snackbars.py deleted file mode 100644 index e69de29..0000000 diff --git a/pythonx/compose/material3/surfaces.py b/pythonx/compose/material3/surfaces.py deleted file mode 100644 index e69de29..0000000 diff --git a/pythonx/compose/material3/swipe_to_dismiss.py b/pythonx/compose/material3/swipe_to_dismiss.py deleted file mode 100644 index e69de29..0000000 diff --git a/pythonx/compose/material3/switch.py b/pythonx/compose/material3/switch.py deleted file mode 100644 index e69de29..0000000 diff --git a/pythonx/compose/material3/tabs.py b/pythonx/compose/material3/tabs.py deleted file mode 100644 index e69de29..0000000 diff --git a/pythonx/compose/material3/time_picker.py b/pythonx/compose/material3/time_picker.py deleted file mode 100644 index e69de29..0000000 diff --git a/pythonx/compose/material3/tool_tip.py b/pythonx/compose/material3/tool_tip.py deleted file mode 100644 index e69de29..0000000 diff --git a/pythonx/compose/material3/top_app_bar.py b/pythonx/compose/material3/top_app_bar.py deleted file mode 100644 index e69de29..0000000 diff --git a/pythonx/compose/material3/typography.py b/pythonx/compose/material3/typography.py deleted file mode 100644 index e69de29..0000000 diff --git a/pythonx/compose/wrapper/__init__.py b/pythonx/compose/wrapper/__init__.py deleted file mode 100644 index 79d80a5..0000000 --- a/pythonx/compose/wrapper/__init__.py +++ /dev/null @@ -1,11 +0,0 @@ -"""`pythonx/compose/wrapper/__init__.py` -- 2024 `KotlinWrapper` class mutation trick, retired. - -## What used to be here, and why it is gone - -`KotlinWrapper` reclassed live Java objects onto a Python class (`kotlin_object.__class__ = cls`). -That trick was Chaquopy-specific. - -Under `PythonMultiplatform`, Kotlin objects are represented as `ObjectReference` integer handles in a -`HandleTable`, and an `int` does not support `__class__` assignment. Proxy classes and method attachment -are now handled by `PythonxAdapter`. -""" diff --git a/tests/test_legacy_modules.py b/tests/test_legacy_modules.py index e801682..ef43f0a 100644 --- a/tests/test_legacy_modules.py +++ b/tests/test_legacy_modules.py @@ -5,7 +5,6 @@ - `pythonx/compose/lite/app.py`, `jvm.py`, `material3.py`, `runtime.py` - `pythonx/compose/test/main.py` - `pythonx/compose/ui/unit/__init__.py` and `pythonx/compose/ui/unit/dp.py` -- `pythonx/compose/wrapper/__init__.py` - `pythonx/compose/material3/__init__.py` """ @@ -25,7 +24,6 @@ TEST_MAIN = REPO / "pythonx" / "compose" / "test" / "main.py" UI_UNIT_INIT = REPO / "pythonx" / "compose" / "ui" / "unit" / "__init__.py" UI_UNIT_DP = REPO / "pythonx" / "compose" / "ui" / "unit" / "dp.py" -WRAPPER_INIT = REPO / "pythonx" / "compose" / "wrapper" / "__init__.py" MATERIAL3_INIT = REPO / "pythonx" / "compose" / "material3" / "__init__.py" @@ -96,17 +94,6 @@ def test_ui_unit_init_has_no_dead_relative_import_tokens(self) -> None: ) -class TestWrapperModuleNoDeadTokens(unittest.TestCase): - def test_wrapper_init_has_no_chaquopy_class_mutation_tokens(self) -> None: - tokens = _get_executable_tokens(WRAPPER_INIT) - for banned in ("KotlinWrapper", "__class__"): - self.assertNotIn( - banned, - tokens, - f"pythonx/compose/wrapper/__init__.py still contains Chaquopy wrapper token: {banned}", - ) - - class TestMaterial3InitNoPlaceholderStarImports(unittest.TestCase): def test_material3_init_has_no_dead_star_imports_of_placeholder_files(self) -> None: tokens = _get_executable_tokens(MATERIAL3_INIT) @@ -143,7 +130,6 @@ class TestRetiredModulesExposeTheirExplanationAsADocstring(unittest.TestCase): "pythonx/compose/ui/unit/dp.py", "pythonx/compose/ui/unit/__init__.py", "pythonx/compose/test/main.py", - "pythonx/compose/wrapper/__init__.py", "pythonx/compose/lite/material3.py", "pythonx/compose/lite/runtime.py", "pythonx/compose/lite/app.py", diff --git a/tests/test_material3_module.py b/tests/test_material3_module.py index 8543470..7224424 100644 --- a/tests/test_material3_module.py +++ b/tests/test_material3_module.py @@ -222,77 +222,22 @@ def test_the_package_imports_with_no_binder_installed(self): self.assertNotIn("Icon", vars(module)) -class TheTwoUnreachableWrappersLoadWithoutCrashing(unittest.TestCase): - """`icon.py` (`Icon`) and `color_scheme.py` (`ColorScheme`/`lightColorScheme`/`darkColorScheme`) - are the two declarations `PythonMultiplatform` commit `a6742a1c` pinned as *unreachable* through - the walked table, not merely unconfirmed: `Icon` needs an `ImageBitmap`/`ImageVector`/`Painter` - nothing walked can produce, and the two color-scheme factories need 36 `Color` parameters against - the binding's 6-parameter omission cap. "Pinned unreachable" is a different conclusion from the - evidence that deleted `text.py`, `icon_button.py` and now `text_field.py`: those were proven - *redundant* (the walked table draws the same declaration with no wrapper), so the wrapper could - go. `Icon`/`ColorScheme` were proven *uncallable any way at all*, which makes the hand-written - wrapper the only Python-facing record of that fact -- so `text_field.py`'s reasoning does not - apply and these two files stay, per this task's own instruction. - - Both files still wrote the 2024 shape, `class Icon(Composable):` / - `class ColorScheme(Composable):`, inheriting from `pythonx.compose.runtime.Composable`. Commit - `7d6c0a1` replaced that name's *class* with a plain identity-decorator *function* -- correct for - `UI.ipynb`'s `@Composable def Screen(): ...` usage, but a function cannot be a base class, so the - `class` statement itself now raises `TypeError` the moment either file is loaded. Because - `pythonx/compose/material3/__init__.py` imports both unconditionally (`from .icon import *`, - `from .color_scheme import *`), that `TypeError` previously took down `import - pythonx.compose.material3` as a whole -- every other, working declaration (`Button`, `Card`, - `Text`, ...) included, not just the two that are genuinely unreachable. That is the regression - this class checks is fixed: loading each file must not raise, which it can satisfy without - claiming `Icon`/`ColorScheme` now render (they still cannot -- see the class docstrings in each - file) by simply not inheriting from something that is no longer a class. +class TheDeadFilesAreGone(unittest.TestCase): + """`material3/` holds the package `__init__.py` and nothing else. + + `icon.py` and `color_scheme.py` were dead reflection code (a mangled-name search INTENT section 3 + excludes) that nothing imported any more, and 29 sibling files were zero bytes since before the + adaptation layer existed. What `icon.py` recorded -- `Icon` needs an `ImageVector` nothing bound + produces, and the colour-scheme factories need 36 `Color` parameters -- is in SPEC S5.3. Deleted + with the ecosystem lead's approval (pythonx-compose #31). """ - def setUp(self): - # `androidx` is not installed in this pure-Python checkout (`docs/pythonx-adapter-design.md` - # explains why: the real binding lives across a JVM/native boundary this repository's test - # suite has no access to). Both files only *reference* a handful of attributes off - # `androidx.compose.material3` at module scope and never call anything on them at import - # time, so a bare stand-in that has those attributes is enough to reach the `class` statement - # this test is actually about. - stub = types.ModuleType("androidx.compose.material3") - stub.IconKt = object() - stub.ColorSchemeKt = object() - stub.ColorScheme = object() - self._previous = { - name: sys.modules.get(name) - for name in ("androidx", "androidx.compose", "androidx.compose.material3") - } - sys.modules["androidx"] = types.ModuleType("androidx") - sys.modules["androidx.compose"] = types.ModuleType("androidx.compose") - sys.modules["androidx.compose.material3"] = stub - self.addCleanup(self._restore_androidx) - - def _restore_androidx(self): - for name, previous in self._previous.items(): - if previous is None: - sys.modules.pop(name, None) - else: - sys.modules[name] = previous - - def test_icon_module_loads_without_raising(self): - module = _load_by_path(MATERIAL3_DIR / "icon.py", "_test_material3_icon") - self.assertTrue(hasattr(module, "Icon")) - - def test_color_scheme_module_loads_without_raising(self): - module = _load_by_path(MATERIAL3_DIR / "color_scheme.py", "_test_material3_color_scheme") - self.assertTrue(hasattr(module, "ColorScheme")) - self.assertTrue(hasattr(module, "lightColorScheme")) - self.assertTrue(hasattr(module, "darkColorScheme")) - - def test_neither_file_inherits_from_the_now_functional_composable(self): - for filename in ("icon.py", "color_scheme.py"): - source = (MATERIAL3_DIR / filename).read_text(encoding="utf-8") - self.assertNotIn( - "(Composable)", source, - f"{filename} still subclasses Composable, which 7d6c0a1 made a plain function -- " - "that raises TypeError the moment the class statement runs", - ) + def test_only_the_package_init_remains(self): + files = sorted(p.name for p in MATERIAL3_DIR.glob("*.py")) + self.assertEqual(["__init__.py"], files) + + def test_the_wrapper_package_is_gone(self): + self.assertFalse((REPO / "pythonx" / "compose" / "wrapper").exists()) if __name__ == "__main__":