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
5 changes: 5 additions & 0 deletions .github/workflows/publish-pypi.yml
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,11 @@ jobs:
- name: Build the sdist and the wheel
run: python -m build
- run: twine check dist/*
- name: Keep pythonx a namespace package
run: |
if unzip -l dist/*.whl | grep -q ' pythonx/__init__.py$'; then
echo 'pythonx/__init__.py would hide the other pythonx libraries'; exit 1
fi
# Importing needs no binder: names resolve lazily, on first use.
- name: Smoke-install the wheel in a fresh venv
run: |
Expand Down
5 changes: 5 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -188,6 +188,11 @@ worktree so a test can see it. Read it in place.
depending on it is allowed, and this package keeps no member resolver or keyword wrapper of its
own. What stays here: the module grouping, the snake-case-only `pythonx` surface, aliases, objects
and grouped constants, the value-class allowlist, the app root and the Pythonic stubs.
- **`pythonx` is a namespace package shared by every pythonx library.** Never add
`pythonx/__init__.py`: pythonx-compose, pythonx-concurrent, pythonx-graphics and pythonx-platform
all install into `pythonx`, and an `__init__.py` from any one of them would hide the others. This
package owns only `pythonx/compose/`. `tests/test_wheel.py` and the publish workflow refuse a
wheel that contains `pythonx/__init__.py`.
- `pythonx/` is a **real package on disk**. Its modules import the `androidx.compose.*` modules the
binder exposes and restructure them Pythonically. A design in which something else synthesises
`pythonx.*` (for example with `__path__ = []`) and the on-disk files cannot be imported is ruled
Expand Down
4 changes: 2 additions & 2 deletions PROJECT.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,8 @@ Compose(`androidx.compose.*`)를 파이썬에서 쓰기 위한 pip 패키지 **`

```
uv run --with pytest --with mypy pytest tests -q
PythonMultiplatform 체크아웃 없음 151 passed, 123 skipped
python-multiplatform develop 31c092f0+ 273 passed, 1 skipped
PythonMultiplatform 체크아웃 없음 152 passed, 123 skipped
python-multiplatform develop 31c092f0+ 274 passed, 1 skipped
describe_member 없는 체크아웃 같은 수, 그중 7 개 건너뜀 (python-multiplatform #54 이전)
describe(module, name) 없는 체크아웃 같은 수, 그중 13 개 건너뜀 (python-multiplatform #36 이전)
그보다 오래된 체크아웃 거기에 5 개 더 건너뜀 (#131 이전, snake_case 이름 없음)
Expand Down
4 changes: 2 additions & 2 deletions docs/SPEC.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,8 @@ Run from a worktree with `uv run --with pytest --with mypy pytest tests -q -rs`

| Environment | Result |
|---|---|
| No `PythonMultiplatform` checkout found | **151 passed, 123 skipped**, 186 subtests passed |
| python-multiplatform `develop` at `31c092f0` or later (property rows, #38; `describe_member`, #54; besides `add_member_resolver` and `describe(module, name)`) | **273 passed, 1 skipped**, 232 subtests passed |
| No `PythonMultiplatform` checkout found | **152 passed, 123 skipped**, 186 subtests passed |
| python-multiplatform `develop` at `31c092f0` or later (property rows, #38; `describe_member`, #54; besides `add_member_resolver` and `describe(module, name)`) | **274 passed, 1 skipped**, 232 subtests passed |
| an older checkout, before python-multiplatform #131 (no binder-side snake_case names) | the same, with the snake_case method tests skipped |
| an older one, without `describe(module, name)` (python-multiplatform #36) | with 13 more skipped again |
| an older one still, without `add_member_resolver` | with 5 more skipped again |
Expand Down
2 changes: 1 addition & 1 deletion docs/guide/status.html
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@
<thead><tr><th><span data-lang="en">State</span><span data-lang="ko">상태</span></th><th><span data-lang="en">Item</span><span data-lang="ko">항목</span></th></tr></thead>
<tbody><tr><td><span class="chip ok"><span data-lang="en">implemented</span><span data-lang="ko">구현</span></span></td><td><span data-lang="en">Mapping manifest <code>pythonx/compose/pythonx-map.toml</code>: inside the package, ships in the wheel</span><span data-lang="ko">매핑 매니페스트 <code>pythonx/compose/pythonx-map.toml</code>: 패키지 안에 있으며 wheel 에 포함</span></td></tr><tr><td><span class="chip ok"><span data-lang="en">implemented</span><span data-lang="ko">구현</span></span></td><td><span data-lang="en"><code>@Composable</code> identity decorator</span><span data-lang="ko"><code>@Composable</code> 항등 데코레이터</span></td></tr><tr><td><span class="chip ok"><span data-lang="en">implemented</span><span data-lang="ko">구현</span></span></td><td><span data-lang="en"><code>pythonx</code> as a real on-disk package re-exporting <code>androidx.compose.*</code> by one rule: names, keywords, signatures, <code>Dp</code> allowlist</span><span data-lang="ko"><code>androidx.compose.*</code> 를 규칙 하나로 재노출하는 실제 디스크 패키지 <code>pythonx</code>: 이름, 키워드, 시그니처, <code>Dp</code> 허용 목록</span></td></tr><tr><td><span class="chip ok"><span data-lang="en">implemented</span><span data-lang="ko">구현</span></span></td><td><span data-lang="en"><code>Modifier</code> chains with snake_case methods, overload dispatch, <code>Dp</code> as a number</span><span data-lang="ko">snake_case 메서드를 쓰는 <code>Modifier</code> 체인, 오버로드 디스패치, 숫자 <code>Dp</code></span></td></tr><tr><td><span class="chip ok"><span data-lang="en">implemented</span><span data-lang="ko">구현</span></span></td><td><span data-lang="en"><code>Column</code>, <code>Row</code>, <code>Spacer</code> importable from <code>material3</code> as well as <code>layout</code> (manifest <code>[aliases]</code>)</span><span data-lang="ko"><code>layout</code> 뿐 아니라 <code>material3</code> 에서도 import 되는 <code>Column</code>, <code>Row</code>, <code>Spacer</code> (매니페스트 <code>[aliases]</code>)</span></td></tr><tr><td><span class="chip ok"><span data-lang="en">implemented</span><span data-lang="ko">구현</span></span></td><td><span data-lang="en">Type stubs (<code>.pyi</code>) generated from real Compose 1.11.1 by the runtime's naming rule, with <code>py.typed</code>, checked with mypy (#12); many types are still <code>Any</code></span><span data-lang="ko">런타임과 같은 이름 규칙으로 실제 Compose 1.11.1 에서 생성한 타입 스텁(<code>.pyi</code>)과 <code>py.typed</code>, mypy 로 검사(#12); 아직 많은 타입이 <code>Any</code> 입니다</span></td></tr><tr><td><span class="chip ok"><span data-lang="en">implemented</span><span data-lang="ko">구현</span></span></td><td><span data-lang="en">Snake-case names, keywords and signatures: modules and methods; the names and keywords on a proxy are the binder's since python-multiplatform #131 (an older binder spells them in Kotlin), and Kotlin's own spelling still works at run time</span><span data-lang="ko">snake_case 이름·키워드·시그니처: 모듈과 메서드; 프록시의 이름과 키워드는 python-multiplatform #131 부터 바인더가 제공하고(오래된 바인더는 Kotlin 이름으로 씁니다), Kotlin 이름도 실행 시에는 그대로 통합니다</span></td></tr><tr><td><span class="chip partial"><span data-lang="en">partial</span><span data-lang="ko">부분</span></span></td><td><span data-lang="en">The empty <code>Modifier</code>: <code>Modifier.padding(16)</code> from the class needs an app-supplied factory against real Compose</span><span data-lang="ko">빈 <code>Modifier</code>: 클래스에서 시작하는 <code>Modifier.padding(16)</code> 은 실제 Compose 에서 앱이 제공하는 팩토리가 필요</span></td></tr><tr><td><span class="chip ok"><span data-lang="en">implemented</span><span data-lang="ko">구현</span></span></td><td><span data-lang="en"><code>Alignment</code> / <code>Arrangement</code>: <code>Alignment.Center</code>, <code>Arrangement.End</code>, <code>Arrangement.spaced_by(8)</code>, and the grouped <code>Alignment.Horizontal.End</code> beside <code>Alignment.End</code></span><span data-lang="ko"><code>Alignment</code> / <code>Arrangement</code>: <code>Alignment.Center</code>, <code>Arrangement.End</code>, <code>Arrangement.spaced_by(8)</code>, 그리고 <code>Alignment.End</code> 와 나란히 묶음 표기 <code>Alignment.Horizontal.End</code></span></td></tr><tr><td><span class="chip partial"><span data-lang="en">partial</span><span data-lang="ko">부분</span></span></td><td><span data-lang="en">Material 3 widgets: re-exported by rule; render proofs per widget pending (#9)</span><span data-lang="ko">Material 3 위젯: 규칙으로 재노출됨; 위젯별 렌더 증거 대기(#9)</span></td></tr><tr><td><span class="chip partial"><span data-lang="en">partial</span><span data-lang="ko">부분</span></span></td><td><span data-lang="en">Distribution as <code>pythonx-compose</code>: the wheel carries the manifest, the re-export rule, the type stubs and <code>py.typed</code></span><span data-lang="ko"><code>pythonx-compose</code> 배포: wheel 에 매니페스트, 재노출 규칙, 타입 스텁, <code>py.typed</code> 포함</span></td></tr><tr><td><span class="chip partial"><span data-lang="en">partial</span><span data-lang="ko">부분</span></span></td><td><span data-lang="en">A declared app root (<code>@app</code>) and Pythonic state (<code>state</code>), with no update call: binder path tested against a fake host shaped after python-multiplatform #38; real Compose is the E2E module (#11, #19)</span><span data-lang="ko">갱신 호출 없는 선언형 앱 루트(<code>@app</code>)와 파이썬다운 상태(<code>state</code>): 바인더 경로는 python-multiplatform #38 모양의 가짜 호스트로 테스트됨, 실제 Compose 는 E2E 모듈(#11, #19)</span></td></tr><tr><td><span class="chip partial"><span data-lang="en">partial</span><span data-lang="ko">부분</span></span></td><td><span data-lang="en"><code>DefaultIcons</code> is <code>Icons.Default</code>, written <code>DefaultIcons.Add</code>, tested against a fake host shaped after python-multiplatform #37/#38; the real draw is in python-multiplatform's render test</span><span data-lang="ko"><code>DefaultIcons</code> 는 <code>Icons.Default</code> 이며 <code>DefaultIcons.Add</code> 로 씁니다. python-multiplatform #37/#38 모양의 가짜 호스트로 테스트됨; 실제 그리기는 python-multiplatform 의 렌더 테스트에 있음</span></td></tr><tr><td><span class="chip partial"><span data-lang="en">partial</span><span data-lang="ko">부분</span></span></td><td><span data-lang="en"><code>TextField(state=…)</code> with <code>TextFieldState</code> from <code>pythonx.compose.foundation.text.input</code>: tested against a fake host shaped after python-multiplatform #73; the input-method (IME) composing proof is python-multiplatform E2E #26 (#10)</span><span data-lang="ko"><code>pythonx.compose.foundation.text.input</code> 의 <code>TextFieldState</code> 를 쓰는 <code>TextField(state=…)</code>: python-multiplatform #73 모양의 가짜 호스트로 테스트됨; 입력기(IME) 조합 증거는 python-multiplatform E2E #26 (#10)</span></td></tr><tr><td><span class="chip planned"><span data-lang="en">planned</span><span data-lang="ko">계획</span></span></td><td><span data-lang="en">Colour schemes (36 colour parameters)</span><span data-lang="ko">색 스킴 (색 매개변수 36 개)</span></td></tr><tr><td><span class="chip partial"><span data-lang="en">partial</span><span data-lang="ko">부분</span></span></td><td><span data-lang="en"><code>remember_saveable</code> with the notebook's <code>getValue()</code> / <code>setValue()</code>: tested here against a fake host; saving and restoring is python-multiplatform's test (#174)</span><span data-lang="ko">노트북의 <code>getValue()</code> / <code>setValue()</code> 를 쓰는 <code>remember_saveable</code>: 여기서는 가짜 호스트로 테스트, 저장과 복원은 python-multiplatform 의 테스트(#174)</span></td></tr><tr><td><span class="chip planned"><span data-lang="en">planned</span><span data-lang="ko">계획</span></span></td><td><span data-lang="en">Coroutine scopes (after pythonx-concurrent)</span><span data-lang="ko">코루틴 스코프 (pythonx-concurrent 이후)</span></td></tr></tbody>
</table></div>
<p data-lang="en" class="muted">Test baseline: <code>151 passed, 123 skipped</code> without a python-multiplatform checkout; <code>273 passed, 1 skipped</code> with a current one. The one skip with a current checkout reads the maintainer's <code>UI.ipynb</code>.</p><p data-lang="ko" class="muted">테스트 기준선: python-multiplatform 체크아웃이 없으면 <code>151 passed, 123 skipped</code>, 최신 체크아웃이면 <code>273 passed, 1 skipped</code>. 최신 체크아웃에서 건너뛰는 1 개는 메인테이너의 <code>UI.ipynb</code> 를 읽는 테스트입니다.</p>
<p data-lang="en" class="muted">Test baseline: <code>152 passed, 123 skipped</code> without a python-multiplatform checkout; <code>274 passed, 1 skipped</code> with a current one. The one skip with a current checkout reads the maintainer's <code>UI.ipynb</code>.</p><p data-lang="ko" class="muted">테스트 기준선: python-multiplatform 체크아웃이 없으면 <code>152 passed, 123 skipped</code>, 최신 체크아웃이면 <code>274 passed, 1 skipped</code>. 최신 체크아웃에서 건너뛰는 1 개는 메인테이너의 <code>UI.ipynb</code> 를 읽는 테스트입니다.</p>
</section>
<section>
<h2>FAQ</h2>
Expand Down
4 changes: 4 additions & 0 deletions tests/test_wheel.py
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,10 @@ def test_no_build_tooling_or_tests_ship(self):
with self.subTest(prefix=prefix):
self.assertEqual([], sorted(n for n in self.names if n.startswith(prefix)))

def test_pythonx_stays_a_namespace_package(self):
# Every pythonx library installs into `pythonx`; an `__init__.py` from one would hide the rest.
self.assertNotIn("pythonx/__init__.py", self.names)

def test_the_re_export_rule_ships(self):
self.assertIn("pythonx/compose/_reexport.py", self.names)

Expand Down
Loading