Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -184,8 +184,8 @@ worktree so a test can see it. Read it in place.
`.tmp/` (rule 2), not in the home directory.
- Tests that exercise the binder's adaptation layer read it, read-only, from a `PythonMultiplatform`
checkout: the sibling directory `../PythonMultiplatform`, or `PYTHONMULTIPLATFORM_HOME`. Without
one they **skip**; with one, 4 of them still skip until the binder's member resolver lands
(python-multiplatform #17; see `docs/SPEC.md` §0). Report both numbers and say which environment you ran in. A skip is not
one they **skip**; with a checkout older than python-multiplatform `ba4c6f49`, 5 of them skip for
want of its member resolver (see `docs/SPEC.md` §0). Report both numbers and say which environment you ran in. A skip is not
a pass.
- `PythonMultiplatform` is read-only from this repository. Never write to it from here.
- Record the before and after counts of every change; a change that only touches documentation
Expand Down
11 changes: 6 additions & 5 deletions PROJECT.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,19 +28,20 @@ Compose(`androidx.compose.*`)를 파이썬에서 쓰기 위한 pip 패키지 **`
| 상태 | 항목 |
|---|---|
| 구현 | 매핑 매니페스트 `pythonx-map.toml`, `@Composable` 항등 데코레이터, 실제 디스크 패키지 `pythonx` 와 모듈 수준 규칙 기반 재노출(snake_case 이름·키워드·시그니처, 오버로드 디스패치, 값 클래스 허용 목록) |
| 부분 | 배포 설정(스텁·`py.typed` 없음, 매니페스트가 패키지 밖), proxy 위의 snake_case 확장 메서드(`m.fill_max_width()`, python-multiplatform #17 대기), Material 3 위젯(렌더 증거는 python-multiplatform 에만), `Icon`·색 스킴(import 만 됨), `Alignment`/`Arrangement` 상수(괄호 없이 읽음, 묶음 표기는 #9) |
| 부분 | 배포 설정(스텁·`py.typed` 없음, 매니페스트가 패키지 밖), proxy 메서드의 키워드 인자는 아직 Kotlin 이름(`m.padding(paddingValues=...)`), Material 3 위젯(렌더 증거는 python-multiplatform 에만), `Icon`·색 스킴(import 만 됨), `Alignment`/`Arrangement` 상수(괄호 없이 읽음, 묶음 표기는 #9) |
| 계획 | `.pyi` 동봉, 선언형 앱 루트(#11), `remember_saveable`·`DefaultIcons`·코루틴 스코프 |

### 테스트 기준선

```
python3 -m pytest tests -q
PythonMultiplatform 체크아웃 없음 50 passed, 46 skipped
PYTHONMULTIPLATFORM_HOME 지정 92 passed, 4 skipped (4 개는 python-multiplatform #17 대기)
PythonMultiplatform 체크아웃 없음 50 passed, 47 skipped
python-multiplatform develop ba4c6f49+ 97 passed
그보다 오래된 체크아웃 92 passed, 5 skipped (member resolver 없음)
```

건너뛰는 4 개는 바인더가 돌려준 proxy 위의 camelCase 확장을 snake_case 이름으로 부르는 테스트다.
바인더의 member resolver(python-multiplatform #17)가 들어오면 실행된다. 건너뜀은 통과가 아니다.
member resolver(python-multiplatform `ba4c6f49`)가 없는 바인더에서는 proxy 의 snake_case 메서드를 부르는
5 개가 건너뛴다. 건너뜀은 통과가 아니다.
체크아웃 없이 통과하는 50 개 중 다수는 *부재*(옛 토큰·삭제된 파일이 없음)를 확인하는 것이라 기능 진척으로 세지
않는다.

Expand Down
42 changes: 23 additions & 19 deletions docs/SPEC.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,14 @@ Run from a worktree with `python3 -m pytest tests -q` (pytest 8, CPython 3.13):

| Environment | Result |
|---|---|
| No `PythonMultiplatform` checkout found | **50 passed, 46 skipped**, 66 subtests passed |
| `PYTHONMULTIPLATFORM_HOME` → the `PythonMultiplatform` checkout | **92 passed, 4 skipped**, 72 subtests passed |
| No `PythonMultiplatform` checkout found | **50 passed, 47 skipped**, 66 subtests passed |
| python-multiplatform `develop` at `ba4c6f49` or later (has `add_member_resolver`) | **97 passed**, 72 subtests passed |
| an older checkout, without `add_member_resolver` | **92 passed, 5 skipped**, 72 subtests passed |

Without a checkout, the 46 tests that install the binder's layers through `tests/adapter.py` skip.
With one, all pass except 4 that skip because they call a camelCase extension on a binder proxy by
its snake_case name, which needs python-multiplatform #17 (§3). A skip is not a pass.
Without a checkout, the 47 tests that install the binder's layers through `tests/adapter.py` skip.
Against a binder older than `ba4c6f49`, the 5 that call a snake_case method on a proxy the binder
returned skip, because that needs its member resolver (python-multiplatform #17). A skip is not a
pass.

Of the 50 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.
Expand Down Expand Up @@ -107,23 +109,26 @@ Evidence: `tests/test_runtime_module.py::TheModuleIsTheFileOnDisk`,
real Python and the fake host. Disabling the name rule fails 15 tests, the keyword mapping 1, the
allowlist priming 12.

Not covered by this: **methods on a proxy the binder returned** (`m.fill_max_width()`). Those are
attributes of the binder's own class, which this package can only reach through the member-resolver
hook python-multiplatform #17 adds; until then a camelCase extension is reachable on a proxy only by
its Kotlin name, and four tests skip (S4.1, S6.1, S6.4).
Methods on a proxy the binder returned (`m.fill_max_width()`) are attributes of the binder's own
class. The same rule reaches them as the binder's member resolver (`member_name`, registered through
`python_multiplatform.binding.add_member_resolver`, python-multiplatform `ba4c6f49`); the binder
caches the alias in its own registry, so its class keeps Kotlin names in `dir()` (issue #20).
Keyword arguments to such a method are still Kotlin's own (`m.padding(paddingValues=...)`): the
resolver maps names, not keywords.

## 4. Naming

### S4.1 Kotlin parameters, `snake_case` — `partial`
### S4.1 Kotlin parameters, `snake_case` — `partial` (method keywords)

`onClick` → `on_click`, `fillMaxWidth` → `fill_max_width`, `zIndex` → `z_index`,
`toURLString` → `to_url_string`; type names (`Modifier`) unchanged; an explicit overload keeps its
type suffix (`padding__Dp_Dp`); a name the reverse rule cannot invert still resolves, because the
rule is only ever applied forward to the module's real names; the camelCase spelling is not a second
name.

Implemented for module-level names and keywords (`tests/test_chain.py::Names`, 8 tests). Partial
because receiver methods on a binder proxy need python-multiplatform #17 (§3, issue #20).
Implemented for module-level names and keywords and for method names on a binder proxy
(`tests/test_chain.py::Names`, 8 tests; `TheChain`). Partial because a method's keyword arguments are
still Kotlin's (§3).

### S4.2 The notebook's spellings are examples, not the contract — `implemented` as a rule

Expand Down Expand Up @@ -167,16 +172,16 @@ the two colour-scheme factories take 36 `Color` parameters. The notebook uses `I

## 6. Modifiers — extension functions as methods

### S6.1 `Modifier` extensions are methods on the receiver proxy — `partial`
### S6.1 `Modifier` extensions are methods on the receiver proxy — `implemented`

`Modifier.padding(16).size(24)` chains; each link returns a new receiver; an unbound name raises
`AttributeError` naming where it looked; the class-object spelling without a registered empty
factory raises `TypeError` naming `register_empty`; `pythonx.compose.ui.Modifier` is the binder's
proxy class itself.

Evidence: `tests/test_chain.py::TheChain` (6 tests, 1 skipped) and
`tests/test_modifier_module.py::TheModifierSeam` (5 tests). Partial because a camelCase extension
(`fill_max_width`) on a proxy waits for python-multiplatform #17.
Evidence: `tests/test_chain.py::TheChain` (8 tests) and
`tests/test_modifier_module.py::TheModifierSeam` (5 tests), against python-multiplatform `ba4c6f49`.
A snake_case method resolved through the member rule leaves the binder's class Kotlin-named.

### S6.2 The empty-`Modifier` seam — `partial`

Expand All @@ -195,14 +200,13 @@ declared type; a non-match names the candidates; an explicit overload spelling (
bypasses dispatch; the module function takes snake_case keywords. Evidence:
`tests/test_chain.py::OverloadDispatch` (7 tests). Status: `implemented`.

### S6.4 Value classes — `partial`
### S6.4 Value classes — `implemented`

A raw number is accepted for a `Dp` parameter; a `Dp` value is accepted too; a plain `Float`
parameter is not treated as a value class; a packed value class (`TextUnit`) refuses a raw number and
says why; the allow-list can be extended at run time. The allow-list itself is the manifest's
(S2, implemented) and reaches the binder when a `pythonx.compose` module first resolves a name.
Runtime evidence: `tests/test_chain.py::ValueClasses` (6 tests, 3 skipped until python-multiplatform
#17, because they call camelCase extensions on a proxy).
Runtime evidence: `tests/test_chain.py::ValueClasses` (6 tests).

### S6.5 Lazy resolution and handle lifetime — `implemented`

Expand Down
4 changes: 2 additions & 2 deletions docs/guide/getting-started.html
Original file line number Diff line number Diff line change
Expand Up @@ -43,9 +43,9 @@ <h2><span data-lang="en">1. Get the source</span><span data-lang="ko">1. 소스
<section>
<h2><span data-lang="en">2. Run the tests</span><span data-lang="ko">2. 테스트 실행</span></h2>
<pre><code class="language-bash">python3 -m pytest tests -q</code></pre>
<p data-lang="en">Expect <strong>50 passed, 46 skipped</strong>. The skipped tests drive the binder's adaptation layer and need a <a href="https://github.com/thisisthepy/python-multiplatform">python-multiplatform</a> checkout beside this one, or:</p><p data-lang="ko"><strong>50 passed, 46 skipped</strong> 가 정상입니다. 건너뛴 테스트는 바인더의 적응 계층을 구동하며, 옆에 <a href="https://github.com/thisisthepy/python-multiplatform">python-multiplatform</a> 체크아웃이 있거나 다음이 필요합니다:</p>
<p data-lang="en">Expect <strong>50 passed, 47 skipped</strong>. The skipped tests drive the binder's adaptation layer and need a <a href="https://github.com/thisisthepy/python-multiplatform">python-multiplatform</a> checkout beside this one, or:</p><p data-lang="ko"><strong>50 passed, 47 skipped</strong> 가 정상입니다. 건너뛴 테스트는 바인더의 적응 계층을 구동하며, 옆에 <a href="https://github.com/thisisthepy/python-multiplatform">python-multiplatform</a> 체크아웃이 있거나 다음이 필요합니다:</p>
<pre><code class="language-bash">PYTHONMULTIPLATFORM_HOME=/path/to/python-multiplatform python3 -m pytest tests -q</code></pre>
<p data-lang="en">With it, all but 4 pass; those 4 <strong>skip</strong> until the binder can resolve a snake_case method on a proxy it returned (python-multiplatform #17). That is expected and recorded.</p><p data-lang="ko">지정하면 4 개를 뺀 전부가 통과합니다. 그 4 개는 바인더가 돌려준 proxy 의 snake_case 메서드를 바인더가 풀 수 있을 때까지(python-multiplatform #17) <strong>건너뜁니다</strong>. 예상된 상태이며 기록되어 있습니다.</p>
<p data-lang="en">With a current one (<code>ba4c6f49</code> 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.</p><p data-lang="ko">최신 체크아웃(<code>ba4c6f49</code> 이후)이면 전부 통과하고, 오래된 체크아웃은 바인더가 돌려준 proxy 의 snake_case 메서드를 부르는 5 개를 <strong>건너뜁니다</strong>. 예상된 상태이며 기록되어 있습니다.</p>
</section>

<section>
Expand Down
Loading