diff --git a/.github/scripts/check_release_version.py b/.github/scripts/check_release_version.py new file mode 100644 index 0000000..4d854a5 --- /dev/null +++ b/.github/scripts/check_release_version.py @@ -0,0 +1,36 @@ +#!/usr/bin/env python3 +"""Fail unless a release tag is `v` + the version in `pyproject.toml`. + + python3 .github/scripts/check_release_version.py "$GITHUB_REF_NAME" # exit 0 = match, 1 = not + +`publish-pypi.yml` runs this before building, so a tag cannot publish a version it does not name. +A full ref (`refs/tags/v0.1.0a1`) is accepted too. Standard library only. +""" + +from __future__ import annotations + +import sys +import tomllib +from pathlib import Path + +REPO = Path(__file__).resolve().parents[2] + + +def main(argv: list[str]) -> int: + if len(argv) != 2: + print("usage: check_release_version.py ", file=sys.stderr) + return 2 + tag = argv[1].removeprefix("refs/tags/") + with open(REPO / "pyproject.toml", "rb") as f: + version = tomllib.load(f)["project"]["version"] + expected = f"v{version}" + if tag != expected: + print(f"error: tag {tag!r} does not match pyproject.toml, which expects {expected!r}.\n" + f"Fix the version in pyproject.toml or tag the right commit.", file=sys.stderr) + return 1 + print(f"ok: tag {tag} matches pyproject.toml version {version}") + return 0 + + +if __name__ == "__main__": + sys.exit(main(sys.argv)) diff --git a/.github/workflows/publish-pypi.yml b/.github/workflows/publish-pypi.yml new file mode 100644 index 0000000..45a71c9 --- /dev/null +++ b/.github/workflows/publish-pypi.yml @@ -0,0 +1,54 @@ +name: Publish to PyPI + +# A pushed `v*` tag releases that commit. The tag must be `v` + the version in pyproject.toml. The +# upload uses PyPI trusted publishing: no token is stored anywhere; PyPI trusts this workflow and +# the `pypi` environment, whose deployment policy allows `v*` tags only. +on: + push: + tags: ['v*'] + +permissions: + contents: read + +jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 1 + - uses: actions/setup-python@v5 + with: + python-version: '3.13' + - run: python -m pip install build twine + - name: Check the tag against pyproject.toml + run: python .github/scripts/check_release_version.py "$GITHUB_REF_NAME" + - name: Build the sdist and the wheel + run: python -m build + - run: twine check dist/* + # Importing needs no binder: names resolve lazily, on first use. + - name: Smoke-install the wheel in a fresh venv + run: | + python -m venv .smoke + .smoke/bin/pip install dist/*.whl + .smoke/bin/python -c "import pythonx.compose, pythonx.compose.ui, pythonx.compose.material3; import importlib.resources as r; assert r.files('pythonx.compose').joinpath('pythonx-map.toml').is_file()" + - uses: actions/upload-artifact@v4 + with: + name: dist + path: dist/ + if-no-files-found: error + + publish: + needs: build + runs-on: ubuntu-latest + environment: + name: pypi + url: https://pypi.org/p/pythonx-compose + permissions: + id-token: write + steps: + - uses: actions/download-artifact@v4 + with: + name: dist + path: dist/ + - uses: pypa/gh-action-pypi-publish@release/v1 diff --git a/.github/workflows/tests.yml b/.github/workflows/test.yml similarity index 98% rename from .github/workflows/tests.yml rename to .github/workflows/test.yml index c6a9f07..da3f22d 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/test.yml @@ -18,7 +18,7 @@ permissions: contents: read concurrency: - group: tests-${{ github.ref }} + group: test-${{ github.ref }} cancel-in-progress: true jobs: diff --git a/AGENTS.md b/AGENTS.md index db7791f..e8f33f4 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -209,7 +209,7 @@ worktree so a test can see it. Read it in place. feature it needs (the member resolver, `ba4c6f49`; `describe(module, name)`, #36; see `docs/SPEC.md` §0). Report both numbers and say which environment you ran in. A skip is not a pass. -- The suite runs on GitHub Actions (`.github/workflows/tests.yml`) in both environments, without and +- The suite runs on GitHub Actions (`.github/workflows/test.yml`) in both environments, without and with a python-multiplatform checkout, for every pull request into `develop`. Local runs are for quick checks of the part you changed; leave the full two-environment run to CI. It also runs daily against python-multiplatform `develop` as it is that day; a failed daily run @@ -222,6 +222,10 @@ worktree so a test can see it. Read it in place. - The distribution name is `pythonx-compose` (`pyproject.toml`); the import package is `pythonx.compose`. +- **Release.** After the maintainer approves, tag the commit to release `v` (the + `pyproject.toml` version, e.g. `v0.1.0a1`) and push the tag; `.github/workflows/publish-pypi.yml` + builds, checks and publishes through PyPI trusted publishing (environment `pypi`). Never upload + by hand, and never store a PyPI token. - `.pyi` stubs, `py.typed` and the manifest must end up **inside the wheel**. A `package-data` pattern is not proof — build the wheel and list it before claiming something ships. diff --git a/PROJECT.md b/PROJECT.md index e06e1b0..cec7da9 100644 --- a/PROJECT.md +++ b/PROJECT.md @@ -35,8 +35,8 @@ Compose(`androidx.compose.*`)를 파이썬에서 쓰기 위한 pip 패키지 **` ``` python3 -m pytest tests -q - PythonMultiplatform 체크아웃 없음 133 passed, 112 skipped - python-multiplatform develop 31c092f0+ 244 passed, 1 skipped + PythonMultiplatform 체크아웃 없음 139 passed, 112 skipped + python-multiplatform develop 31c092f0+ 250 passed, 1 skipped describe_member 없는 체크아웃 같은 수, 그중 7 개 건너뜀 (python-multiplatform #54 이전) describe(module, name) 없는 체크아웃 같은 수, 그중 13 개 건너뜀 (python-multiplatform #36 이전) 그보다 오래된 체크아웃 거기에 5 개 더 건너뜀 (member resolver 없음) @@ -120,6 +120,16 @@ python3 .github/scripts/check_guide.py # 가이드: HTML 파싱, 링 태그 `archive/pre-restructure` 로 되찾을 수 있다. `tools/` 는 없앴다: 스텁 생성기는 `pythonx/_build/`, 가이드 검사기와 릴리스 스크립트는 `.github/scripts/`. +### 결정됨 (2026-10-03, #62) — 첫 PyPI 릴리스 + +- 워크플로 이름: `tests.yml` → `test.yml`, PyPI 배포는 `publish-pypi.yml`. +- 첫 버전은 `0.1.0a1`(알파, 프리릴리스). PyPI 메타데이터(설명·README·MIT·저자·URL·분류자)는 채웠고, + README 의 링크는 pypi.org 에서 열리도록 절대 URL 이다(`main` 링크는 `main` 이 `develop` 을 따라잡은 뒤에 열린다). +- 배포 방법: 릴리스할 커밋에 `v0.1.0a1` 태그를 푸시하면 `publish-pypi.yml` 이 태그와 `pyproject.toml` + 버전을 대조하고, sdist·wheel 을 빌드하고, `twine check`·새 venv 스모크를 거쳐 트러스티드 퍼블리싱으로 + 올린다. **현황: 업로드 전.** 태그 푸시는 메인테이너 승인을 기다리고, GitHub 환경 `pypi`(`v*` 태그만 허용) + 와 PyPI pending publisher 는 메인테이너가 설정한다. + ### 결정됨 (2026-10-03, `docs/INTENT.md` §5) - **선언형 앱 루트, 갱신 함수 없음.** 노트북의 `main.App.update(...)`·`getValue()`/`setValue()` 는 diff --git a/README.md b/README.md index 668de0e..a178167 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -English | [한국어](docs/locale/README_ko.md) +English | [한국어](https://github.com/thisisthepy/pythonx-compose/blob/main/docs/locale/README_ko.md)
@@ -6,12 +6,12 @@ English | [한국어](docs/locale/README_ko.md) **Write Compose user interfaces in Python — with Compose's own widgets, spelled the Python way.** -[![License: MIT](https://img.shields.io/badge/license-MIT-7c4dff.svg)](LICENSE) -[![Python](https://img.shields.io/badge/python-3.11%2B-7c4dff.svg)](pyproject.toml) -[![pip](https://img.shields.io/badge/pip-pythonx--compose-7c4dff.svg)](pyproject.toml) +[![License: MIT](https://img.shields.io/badge/license-MIT-7c4dff.svg)](https://github.com/thisisthepy/pythonx-compose/blob/main/LICENSE) +[![Python](https://img.shields.io/badge/python-3.11%2B-7c4dff.svg)](https://github.com/thisisthepy/pythonx-compose/blob/main/pyproject.toml) +[![pip](https://img.shields.io/badge/pip-pythonx--compose-7c4dff.svg)](https://github.com/thisisthepy/pythonx-compose/blob/main/pyproject.toml) [![Status](https://img.shields.io/badge/status-pre--alpha-lightgrey.svg)](#-status) -[Guide](docs/guide/index.html) · [Getting started](docs/guide/getting-started.html) · [Concepts](docs/guide/concepts.html) · [Status](docs/guide/status.html) +[Guide](https://thisisthepy.github.io/pythonx-compose/) · [Getting started](https://thisisthepy.github.io/pythonx-compose/getting-started.html) · [Concepts](https://thisisthepy.github.io/pythonx-compose/concepts.html) · [Status](https://thisisthepy.github.io/pythonx-compose/status.html)
@@ -56,7 +56,7 @@ proofs are still pending. See [Status](#-status). - **`@Composable` stays.** Screens are decorated Python functions. - **A real package.** `pythonx/` is ordinary Python source that imports `androidx.compose.*` and reshapes it. The binder never renames anything — renaming is this package's job. -- **One manifest.** [`pythonx-map.toml`](pythonx/compose/pythonx-map.toml) says which `pythonx.compose.*` module +- **One manifest.** [`pythonx-map.toml`](https://github.com/thisisthepy/pythonx-compose/blob/main/pythonx/compose/pythonx-map.toml) says which `pythonx.compose.*` module stands for which Kotlin package. It lives inside the package and ships in the wheel. The runtime and the `.pyi` generator read the same file, so what your editor completes is what the interpreter resolves. @@ -86,7 +86,10 @@ Each module is a real file whose `__init__.py` calls one re-export rule; names r ## 🚀 Quick start > [!NOTE] -> `pythonx-compose` is **pre-alpha** and not yet published to PyPI. +> `pythonx-compose` is **pre-alpha**. The first alpha, `0.1.0a1`, is released to PyPI when the +> maintainer tags it; a pre-release needs `pip install --pre pythonx-compose`. + +From a clone, to run the tests: ```bash git clone https://github.com/thisisthepy/pythonx-compose @@ -164,12 +167,12 @@ standalone desktop toolkit. | Colour schemes | ⏳ planned | | `remember_saveable`, coroutine scopes | ⏳ planned | -The full list is on the guide's [Status page](docs/guide/status.html). +The full list is on the guide's [Status page](https://thisisthepy.github.io/pythonx-compose/status.html). ## 📖 Documentation -- **Guide** — [`docs/guide/`](docs/guide/index.html), bilingual (English / 한국어) -- **Korean README** — [`docs/locale/README_ko.md`](docs/locale/README_ko.md) +- **Guide** — [`docs/guide/`](https://thisisthepy.github.io/pythonx-compose/), bilingual (English / 한국어) +- **Korean README** — [`docs/locale/README_ko.md`](https://github.com/thisisthepy/pythonx-compose/blob/main/docs/locale/README_ko.md) ## 🔌 Ecosystem @@ -185,7 +188,7 @@ The full list is on the guide's [Status page](docs/guide/status.html). ## 🤝 Contributing Development is intent-first and test-first: a change starts as a specification change, then a -failing test, then code. The [guide](docs/guide/status.html) lists what is open and where help is +failing test, then code. The [guide](https://thisisthepy.github.io/pythonx-compose/status.html) lists what is open and where help is useful. Please open an issue before large changes. ## Maintainers @@ -197,4 +200,4 @@ useful. Please open an issue before large changes. ## License -[MIT](LICENSE) © 2023–2024 BREW (b-re-w), Jong-uk Lee (rnoro5122) +[MIT](https://github.com/thisisthepy/pythonx-compose/blob/main/LICENSE) © 2023–2024 BREW (b-re-w), Jong-uk Lee (rnoro5122) diff --git a/docs/SPEC.md b/docs/SPEC.md index 27931ed..296a7a8 100644 --- a/docs/SPEC.md +++ b/docs/SPEC.md @@ -21,8 +21,8 @@ Run from a worktree with `python -m pytest tests -q -rs` (pytest 8, mypy 2.4, CP | Environment | Result | |---|---| -| No `PythonMultiplatform` checkout found | **133 passed, 112 skipped**, 167 subtests passed | -| python-multiplatform `develop` at `31c092f0` or later (property rows, #38; `describe_member`, #54; besides `add_member_resolver` and `describe(module, name)`) | **244 passed, 1 skipped**, 198 subtests passed | +| No `PythonMultiplatform` checkout found | **139 passed, 112 skipped**, 167 subtests passed | +| python-multiplatform `develop` at `31c092f0` or later (property rows, #38; `describe_member`, #54; besides `add_member_resolver` and `describe(module, name)`) | **250 passed, 1 skipped**, 198 subtests passed | | an older checkout, without `describe_member` (python-multiplatform #54) | the same, with 7 more 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 | @@ -36,7 +36,7 @@ binder returned skip as well, because that needs its member resolver (python-mul `TheBinderPath` tests (S5.4). A binder without the `@Composable` binding (`push_composer`) skips the 13 text-field tests (S5.5). `tests/test_typing.py` skips where mypy is not installed. A skip is not a pass. -Of the 133 that pass without a checkout, 73 check the type stubs and the wheel (S1.2); most of the +Of the 139 that pass without a checkout, 73 check the type stubs and the wheel (S1.2); most of the rest assert **absence** (a retired token, a deleted file, a docstring that exists). Those are listed in §9 and are not counted as features. @@ -46,13 +46,22 @@ in §9 and are not counted as features. ### S1.1 Installed as `pythonx-compose`, importing `pythonx.compose` — `partial` -`pyproject.toml` declares `name = "pythonx-compose"`, version `0.0.1`, setuptools build, +`pyproject.toml` declares `name = "pythonx-compose"`, version `0.1.0a1`, setuptools build, `requires-python >= 3.11` (the runtime reads the manifest with `tomllib`), and `packages.find include = ["pythonx.compose*"]`. - Shipped and tested: `tests/test_wheel.py` builds the wheel from a copy of the sources and finds `pythonx/compose/pythonx-map.toml`, the re-export rule, and an `__init__.py` for every mapped module (issue #13). The manifest lives inside the package directory so `package-data` carries it. +- PyPI metadata: description, `README.md` as the long description (its links are absolute, since + pypi.org cannot resolve relative ones), `license = "MIT"` with `LICENSE`, the two authors as + `LICENSE` names them (no emails), keywords, classifiers (Alpha, Python 3.11-3.13, Typed) and + project URLs. `build-system.requires` is `setuptools>=77`, which understands that license form. +- Released by pushing a tag `v` (`.github/workflows/publish-pypi.yml`): the workflow checks + the tag equals `v` + the `pyproject.toml` version (`.github/scripts/check_release_version.py`, + `tests/test_release_version.py`), builds the sdist and wheel, runs `twine check`, smoke-installs + the wheel in a fresh venv, then uploads through PyPI trusted publishing (environment `pypi`, no + token). Nothing is uploaded by hand. - Typed: the wheel carries `py.typed` and the generated `.pyi` stubs (S1.2, issue #12). ### S1.2 Type stubs ship in the wheel — `implemented` diff --git a/docs/guide/getting-started.html b/docs/guide/getting-started.html index 33629a7..cb403e6 100644 --- a/docs/guide/getting-started.html +++ b/docs/guide/getting-started.html @@ -30,7 +30,7 @@

Getting started시작하기

From clone to first call클론부터 첫 호출까지

Five minutes, honest about what runs.5 분, 동작하는 범위를 솔직하게.

-

pythonx-compose is pre-alpha and not yet on PyPI. This page shows how to get the source, run the tests, and what you can call today.

pythonx-compose 는 pre-alpha 이며 아직 PyPI 에 없습니다. 이 페이지는 소스를 받고, 테스트를 돌리고, 지금 호출할 수 있는 것을 보여줍니다.

+

pythonx-compose is pre-alpha. The first alpha, 0.1.0a1, is released to PyPI when the maintainer tags it; install a pre-release with pip install --pre pythonx-compose. This page shows how to get the source, run the tests, and what you can call today.

pythonx-compose 는 pre-alpha 입니다. 첫 알파 0.1.0a1 은 메인테이너가 태그를 달면 PyPI 에 배포되며, 프리릴리스는 pip install --pre pythonx-compose 로 설치합니다. 이 페이지는 소스를 받고, 테스트를 돌리고, 지금 호출할 수 있는 것을 보여줍니다.

1. Get the source1. 소스 받기

@@ -43,9 +43,9 @@

1. Get the source1. 소스

2. Run the tests2. 테스트 실행

python3 -m pytest tests -q
-

Expect 133 passed, 112 skipped. One skipped test reads the maintainer's UI.ipynb, which a clone does not have; mypy type-checks the stubs. The other skipped tests drive the binder's adaptation layer and need a python-multiplatform checkout beside this one, or:

133 passed, 112 skipped 가 정상입니다. 건너뛴 테스트 하나는 클론에는 없는 메인테이너의 UI.ipynb 를 읽으며, mypy 는 스텁을 타입 검사합니다. 나머지 건너뛴 테스트는 바인더의 적응 계층을 구동하며, 옆에 python-multiplatform 체크아웃이 있거나 다음이 필요합니다:

+

Expect 139 passed, 112 skipped. One skipped test reads the maintainer's UI.ipynb, which a clone does not have; mypy type-checks the stubs. The other skipped tests drive the binder's adaptation layer and need a python-multiplatform checkout beside this one, or:

139 passed, 112 skipped 가 정상입니다. 건너뛴 테스트 하나는 클론에는 없는 메인테이너의 UI.ipynb 를 읽으며, mypy 는 스텁을 타입 검사합니다. 나머지 건너뛴 테스트는 바인더의 적응 계층을 구동하며, 옆에 python-multiplatform 체크아웃이 있거나 다음이 필요합니다:

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

With a current one (31c092f0 or later) 244 pass and 1 skips (the UI.ipynb test); an older one skips 5 that call a snake_case method on a proxy the binder returned. That is expected and recorded.

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

+

With a current one (31c092f0 or later) 250 pass and 1 skips (the UI.ipynb test); an older one skips 5 that call a snake_case method on a proxy the binder returned. That is expected and recorded.

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

diff --git a/docs/guide/index.html b/docs/guide/index.html index cceb0e6..bddf0b7 100644 --- a/docs/guide/index.html +++ b/docs/guide/index.html @@ -31,7 +31,7 @@
-pre-alpha · pip install pythonx-compose (soon)pre-alpha · pip install pythonx-compose (예정) +pre-alpha · pip install --pre pythonx-composepre-alpha · pip install --pre pythonx-compose

Compose, in Python.Compose 를, 파이썬으로.

The real Compose widgets with their real parameters — restructured into a Python package, spelled the way Python spells things.진짜 Compose 위젯과 진짜 매개변수를 — 파이썬 패키지로 재구성해, 파이썬이 쓰는 방식대로 씁니다.

diff --git a/docs/guide/status.html b/docs/guide/status.html index 0e4e75f..aec2835 100644 --- a/docs/guide/status.html +++ b/docs/guide/status.html @@ -36,11 +36,11 @@ State상태Item항목 implemented구현Mapping manifest pythonx/compose/pythonx-map.toml — inside the package, ships in the wheel매핑 매니페스트 pythonx/compose/pythonx-map.toml — 패키지 안에 있으며 wheel 에 포함implemented구현@Composable identity decorator@Composable 항등 데코레이터implemented구현pythonx as a real on-disk package re-exporting androidx.compose.* by one rule — names, keywords, signatures, Dp allowlistandroidx.compose.* 를 규칙 하나로 재노출하는 실제 디스크 패키지 pythonx — 이름, 키워드, 시그니처, Dp 허용 목록implemented구현Modifier chains with snake_case methods, overload dispatch, Dp as a numbersnake_case 메서드를 쓰는 Modifier 체인, 오버로드 디스패치, 숫자 Dpimplemented구현Column, Row, Spacer importable from material3 as well as layout (manifest [aliases])layout 뿐 아니라 material3 에서도 import 되는 Column, Row, Spacer (매니페스트 [aliases])implemented구현Type stubs (.pyi) generated from real Compose 1.11.1 by the runtime's naming rule, with py.typed, checked with mypy (#12); many types are still Any런타임과 같은 이름 규칙으로 실제 Compose 1.11.1 에서 생성한 타입 스텁(.pyi)과 py.typed, mypy 로 검사(#12); 아직 많은 타입이 Any 입니다implemented구현Snake-case names, keywords and signatures — modules and methods; a method's keywords need python-multiplatform's describe_member, and Kotlin's own spelling still works at run timesnake_case 이름·키워드·시그니처 — 모듈과 메서드; 메서드 키워드에는 python-multiplatform 의 describe_member 가 필요하고, Kotlin 이름도 실행 시에는 그대로 통합니다partial부분The empty Modifier — Modifier.padding(16) from the class needs an app-supplied factory against real Compose빈 Modifier — 클래스에서 시작하는 Modifier.padding(16) 은 실제 Compose 에서 앱이 제공하는 팩토리가 필요implemented구현Alignment / Arrangement — Alignment.Center, Arrangement.End, Arrangement.spaced_by(8), and the grouped Alignment.Horizontal.End beside Alignment.EndAlignment / Arrangement — Alignment.Center, Arrangement.End, Arrangement.spaced_by(8), 그리고 Alignment.End 와 나란히 묶음 표기 Alignment.Horizontal.Endpartial부분Material 3 widgets — re-exported by rule; render proofs per widget pending (#9)Material 3 위젯 — 규칙으로 재노출됨; 위젯별 렌더 증거 대기(#9)partial부분Distribution as pythonx-compose — the wheel carries the manifest, the re-export rule, the type stubs and py.typedpythonx-compose 배포 — wheel 에 매니페스트, 재노출 규칙, 타입 스텁, py.typed 포함partial부분A declared app root (@app) and Pythonic state (state), with no update call — binder path tested against a fake host shaped after python-multiplatform #38; real Compose is the E2E module (#11, #19)갱신 호출 없는 선언형 앱 루트(@app)와 파이썬다운 상태(state) — 바인더 경로는 python-multiplatform #38 모양의 가짜 호스트로 테스트됨, 실제 Compose 는 E2E 모듈(#11, #19)partial부분DefaultIcons is Icons.Default, written DefaultIcons.Add — tested against a fake host shaped after python-multiplatform #37/#38; the real draw is in python-multiplatform's render testDefaultIcons 는 Icons.Default 이며 DefaultIcons.Add 로 씁니다 — python-multiplatform #37/#38 모양의 가짜 호스트로 테스트됨; 실제 그리기는 python-multiplatform 의 렌더 테스트에 있음partial부분TextField(state=…) with TextFieldState from pythonx.compose.foundation.text.input — tested against a fake host shaped after python-multiplatform #73; the input-method (IME) composing proof is python-multiplatform E2E #26 (#10)pythonx.compose.foundation.text.input 의 TextFieldState 를 쓰는 TextField(state=…) — python-multiplatform #73 모양의 가짜 호스트로 테스트됨; 입력기(IME) 조합 증거는 python-multiplatform E2E #26 (#10)planned계획Colour schemes (36 colour parameters)색 스킴 (색 매개변수 36 개)planned계획remember_saveable, coroutine scopesremember_saveable, 코루틴 스코프
-

Test baseline: 133 passed, 112 skipped without a python-multiplatform checkout; 244 passed, 1 skipped with a current one. The one skip with a current checkout reads the maintainer's UI.ipynb.

테스트 기준선: python-multiplatform 체크아웃이 없으면 132 passed, 99 skipped, 최신 체크아웃이면 244 passed, 1 skipped. 최신 체크아웃에서 건너뛰는 1 개는 메인테이너의 UI.ipynb 를 읽는 테스트입니다.

+

Test baseline: 139 passed, 112 skipped without a python-multiplatform checkout; 250 passed, 1 skipped with a current one. The one skip with a current checkout reads the maintainer's UI.ipynb.

테스트 기준선: python-multiplatform 체크아웃이 없으면 132 passed, 99 skipped, 최신 체크아웃이면 250 passed, 1 skipped. 최신 체크아웃에서 건너뛰는 1 개는 메인테이너의 UI.ipynb 를 읽는 테스트입니다.

FAQ

-
Can I pip install it?pip 으로 설치할 수 있나요?

Not yet. It will ship as pythonx-compose; today, clone the repository.

아직은 아닙니다. pythonx-compose 로 배포될 예정이며, 지금은 저장소를 클론하세요.

Do I need to know Kotlin?Kotlin 을 알아야 하나요?

No. The widgets and parameters are Compose's, so Compose documentation applies — read onClick as on_click.

아니요. 위젯과 매개변수가 Compose 의 것이므로 Compose 문서가 그대로 적용됩니다 — onClick 을 on_click 으로 읽으면 됩니다.

Does it run on its own?단독으로 실행되나요?

No. It runs inside an app that embeds CPython through python-multiplatform.

아니요. python-multiplatform 으로 CPython 을 임베딩한 앱 안에서 동작합니다.

Why not look classes up at run time?왜 런타임에 클래스를 찾지 않나요?

GraalVM native images and Kotlin/Native have no reflection, so bindings are generated at build time.

GraalVM 네이티브 이미지와 Kotlin/Native 에는 리플렉션이 없으므로, 바인딩을 빌드 타임에 생성합니다.

What happened to the test/ directory?test/ 디렉터리는 어떻게 되었나요?

It no longer exists. It was a 2023–2024 Kotlin Multiplatform sample from the project's first generation, and it was deleted; the tag archive/pre-restructure keeps it.

더 이상 없습니다. 프로젝트 첫 세대의 2023–2024 Kotlin Multiplatform 샘플이었고 삭제되었으며, 태그 archive/pre-restructure 로 되찾을 수 있습니다.

+
Can I pip install it?pip 으로 설치할 수 있나요?

Yes, once the maintainer tags the first alpha: pip install --pre pythonx-compose (version 0.1.0a1). Until it is out, clone the repository.

메인테이너가 첫 알파를 태그하면 가능합니다: pip install --pre pythonx-compose (버전 0.1.0a1). 배포 전에는 저장소를 클론하세요.

Do I need to know Kotlin?Kotlin 을 알아야 하나요?

No. The widgets and parameters are Compose's, so Compose documentation applies — read onClick as on_click.

아니요. 위젯과 매개변수가 Compose 의 것이므로 Compose 문서가 그대로 적용됩니다 — onClick 을 on_click 으로 읽으면 됩니다.

Does it run on its own?단독으로 실행되나요?

No. It runs inside an app that embeds CPython through python-multiplatform.

아니요. python-multiplatform 으로 CPython 을 임베딩한 앱 안에서 동작합니다.

Why not look classes up at run time?왜 런타임에 클래스를 찾지 않나요?

GraalVM native images and Kotlin/Native have no reflection, so bindings are generated at build time.

GraalVM 네이티브 이미지와 Kotlin/Native 에는 리플렉션이 없으므로, 바인딩을 빌드 타임에 생성합니다.

What happened to the test/ directory?test/ 디렉터리는 어떻게 되었나요?

It no longer exists. It was a 2023–2024 Kotlin Multiplatform sample from the project's first generation, and it was deleted; the tag archive/pre-restructure keeps it.

더 이상 없습니다. 프로젝트 첫 세대의 2023–2024 Kotlin Multiplatform 샘플이었고 삭제되었으며, 태그 archive/pre-restructure 로 되찾을 수 있습니다.

diff --git a/docs/locale/README_ko.md b/docs/locale/README_ko.md index d5e6f66..5a6715a 100644 --- a/docs/locale/README_ko.md +++ b/docs/locale/README_ko.md @@ -1,4 +1,4 @@ -[English](../../README.md) | 한국어 +[English](https://github.com/thisisthepy/pythonx-compose/blob/main/README.md) | 한국어
@@ -6,12 +6,12 @@ **Compose UI 를 파이썬으로 — Compose 의 위젯 그대로, 파이썬다운 이름으로.** -[![License: MIT](https://img.shields.io/badge/license-MIT-7c4dff.svg)](../../LICENSE) -[![Python](https://img.shields.io/badge/python-3.11%2B-7c4dff.svg)](../../pyproject.toml) -[![pip](https://img.shields.io/badge/pip-pythonx--compose-7c4dff.svg)](../../pyproject.toml) +[![License: MIT](https://img.shields.io/badge/license-MIT-7c4dff.svg)](https://github.com/thisisthepy/pythonx-compose/blob/main/LICENSE) +[![Python](https://img.shields.io/badge/python-3.11%2B-7c4dff.svg)](https://github.com/thisisthepy/pythonx-compose/blob/main/pyproject.toml) +[![pip](https://img.shields.io/badge/pip-pythonx--compose-7c4dff.svg)](https://github.com/thisisthepy/pythonx-compose/blob/main/pyproject.toml) [![Status](https://img.shields.io/badge/status-pre--alpha-lightgrey.svg)](#-현황) -[가이드](../guide/index.html) · [시작하기](../guide/getting-started.html) · [개념](../guide/concepts.html) · [현황](../guide/status.html) +[가이드](https://thisisthepy.github.io/pythonx-compose/) · [시작하기](https://thisisthepy.github.io/pythonx-compose/getting-started.html) · [개념](https://thisisthepy.github.io/pythonx-compose/concepts.html) · [현황](https://thisisthepy.github.io/pythonx-compose/status.html)
@@ -54,7 +54,7 @@ def Greeting(): - **`@Composable` 은 그대로.** 화면은 데코레이터가 붙은 파이썬 함수입니다. - **실제 패키지.** `pythonx/` 는 `androidx.compose.*` 를 import 해 재구성하는 평범한 파이썬 소스입니다. 바인더는 아무 이름도 바꾸지 않습니다 — 이름을 바꾸는 것은 이 패키지의 일입니다. -- **하나의 매니페스트.** [`pythonx-map.toml`](../../pythonx/compose/pythonx-map.toml) 이 어떤 `pythonx.compose.*` +- **하나의 매니페스트.** [`pythonx-map.toml`](https://github.com/thisisthepy/pythonx-compose/blob/main/pythonx/compose/pythonx-map.toml) 이 어떤 `pythonx.compose.*` 모듈이 어떤 Kotlin 패키지에 대응하는지 적습니다. 이 파일은 패키지 안에 있으며 wheel 에 함께 담깁니다. 런타임과 `.pyi` 생성기가 같은 파일을 읽으므로, 편집기가 자동완성하는 이름과 인터프리터가 해석하는 이름이 어긋나지 않습니다. @@ -84,7 +84,10 @@ flowchart LR ## 🚀 빠른 시작 > [!NOTE] -> `pythonx-compose` 는 **pre-alpha** 단계이며 아직 PyPI 에 배포되지 않았습니다. +> `pythonx-compose` 는 **pre-alpha** 단계입니다. 첫 알파 `0.1.0a1` 은 메인테이너가 태그를 달면 PyPI 에 +> 배포되며, 프리릴리스이므로 `pip install --pre pythonx-compose` 로 설치합니다. + +클론에서 테스트를 돌리려면: ```bash git clone https://github.com/thisisthepy/pythonx-compose @@ -161,12 +164,12 @@ pip 패키지 **`pythonx-compose`**(파이썬 3.11 이상)로 배포되며, impo | 색 스킴 | ⏳ 계획 | | `remember_saveable`, 코루틴 스코프 | ⏳ 계획 | -전체 목록은 가이드의 [현황 페이지](../guide/status.html)에 있습니다. +전체 목록은 가이드의 [현황 페이지](https://thisisthepy.github.io/pythonx-compose/status.html)에 있습니다. ## 📖 문서 -- **가이드** — [`docs/guide/`](../guide/index.html), 영어 / 한국어 -- **English README** — [`README.md`](../../README.md) +- **가이드** — [`docs/guide/`](https://thisisthepy.github.io/pythonx-compose/), 영어 / 한국어 +- **English README** — [`README.md`](https://github.com/thisisthepy/pythonx-compose/blob/main/README.md) ## 🔌 생태계 @@ -182,7 +185,7 @@ pip 패키지 **`pythonx-compose`**(파이썬 3.11 이상)로 배포되며, impo ## 🤝 기여 개발은 의도 우선, 테스트 우선입니다: 변경은 스펙 변경으로 시작해, 실패하는 테스트를 거쳐, 코드로 -끝납니다. 열린 항목과 도움이 필요한 곳은 [가이드](../guide/status.html)에 있습니다. 큰 변경 전에는 +끝납니다. 열린 항목과 도움이 필요한 곳은 [가이드](https://thisisthepy.github.io/pythonx-compose/status.html)에 있습니다. 큰 변경 전에는 이슈를 먼저 열어 주세요. ## 메인테이너 @@ -194,4 +197,4 @@ pip 패키지 **`pythonx-compose`**(파이썬 3.11 이상)로 배포되며, impo ## 라이선스 -[MIT](../../LICENSE) © 2023–2024 BREW (b-re-w), Jong-uk Lee (rnoro5122) +[MIT](https://github.com/thisisthepy/pythonx-compose/blob/main/LICENSE) © 2023–2024 BREW (b-re-w), Jong-uk Lee (rnoro5122) diff --git a/pyproject.toml b/pyproject.toml index 09857aa..b6797f2 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,10 +1,30 @@ [build-system] -requires = ["setuptools>=42", "wheel"] +requires = ["setuptools>=77", "wheel"] build-backend = "setuptools.build_meta" [project] name = "pythonx-compose" -version = "0.0.1" +version = "0.1.0a1" +description = "Pythonic Compose UI for Python on Kotlin Multiplatform, through python-multiplatform" +readme = {file = "README.md", content-type = "text/markdown"} +license = "MIT" +license-files = ["LICENSE"] +authors = [ + {name = "BREW (b-re-w)"}, + {name = "Jong-uk Lee (rnoro5122)"}, +] +keywords = ["compose", "jetpack-compose", "compose-multiplatform", "kotlin-multiplatform", "ui", "python-multiplatform"] +classifiers = [ + "Development Status :: 3 - Alpha", + "Intended Audience :: Developers", + "Operating System :: OS Independent", + "Programming Language :: Python :: 3", + "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3.12", + "Programming Language :: Python :: 3.13", + "Topic :: Software Development :: User Interfaces", + "Typing :: Typed", +] dependencies = [ ] #dependencies-kotlin = [ @@ -13,6 +33,12 @@ dependencies = [ requires-python = ">= 3.11" # tomllib, which reads the manifest at run time #requires-kotlin = ">= 1.4.32" +[project.urls] +Homepage = "https://github.com/thisisthepy/pythonx-compose" +Source = "https://github.com/thisisthepy/pythonx-compose" +Issues = "https://github.com/thisisthepy/pythonx-compose/issues" +Documentation = "https://thisisthepy.github.io/pythonx-compose/" + [tool.setuptools.packages.find] include = ["pythonx.compose*"] @@ -21,18 +47,3 @@ include = ["pythonx.compose*"] # not descend, so `"pythonx.compose"` alone would ship `pythonx/compose/*.pyi` and drop # `pythonx/compose/ui/*.pyi` -- which is where most of the generated stubs land. "*" = ["*.pyi", "py.typed", "pythonx-map.toml"] -#authors = [ -# {name = "Pradyun Gedam", email = "irack000@gmail.com"}, -# {name = "Tzu-Ping Chung", email = "tzu-ping@example.com"} -#] -#maintainers = [ -# {name = "Brett Cannon", email = "brett@example.com"} -#] -#description = "Lovely Spam! Wonderful Spam!" -#readme = "README.rst" -#license = {file = "LICENSE.txt"} -#keywords = ["egg", "bacon", "sausage", "tomatoes", "Lobster Thermidor"] -#classifiers = [ -# "Development Status :: 4 - Beta", -# "Programming Language :: Python" -#] diff --git a/tests/test_release_version.py b/tests/test_release_version.py new file mode 100644 index 0000000..3753c9b --- /dev/null +++ b/tests/test_release_version.py @@ -0,0 +1,47 @@ +"""The release gate (`.github/scripts/check_release_version.py`): a tag publishes only when it is +`v` + the version in `pyproject.toml`. Run both ways -- a match passes, anything else fails.""" + +from __future__ import annotations + +import subprocess +import sys +import tomllib +import unittest +from pathlib import Path + +REPO = Path(__file__).resolve().parents[1] +SCRIPT = REPO / ".github" / "scripts" / "check_release_version.py" +VERSION = tomllib.loads((REPO / "pyproject.toml").read_text(encoding="utf-8"))["project"]["version"] + + +def _run(tag: str) -> subprocess.CompletedProcess[str]: + return subprocess.run([sys.executable, str(SCRIPT), tag], cwd=REPO, capture_output=True, text=True) + + +class TestReleaseVersion(unittest.TestCase): + def test_matching_tag_passes(self) -> None: + result = _run(f"v{VERSION}") + self.assertEqual(0, result.returncode, result.stdout + result.stderr) + + def test_ref_form_of_the_tag_passes(self) -> None: + self.assertEqual(0, _run(f"refs/tags/v{VERSION}").returncode) + + def test_mismatched_tag_fails_and_names_both(self) -> None: + result = _run("v9.9.9") + self.assertEqual(1, result.returncode) + self.assertIn("v9.9.9", result.stderr) + self.assertIn(f"v{VERSION}", result.stderr) + + def test_tag_without_the_v_prefix_fails(self) -> None: + self.assertEqual(1, _run(VERSION).returncode) + + def test_a_prefix_of_the_version_fails(self) -> None: + self.assertEqual(1, _run(f"v{VERSION}.post1").returncode) + + def test_no_argument_fails(self) -> None: + result = subprocess.run([sys.executable, str(SCRIPT)], cwd=REPO, capture_output=True, text=True) + self.assertNotEqual(0, result.returncode) + + +if __name__ == "__main__": + unittest.main()