Summary
Migrate contributor tooling and CI from pip to uv, with a committed uv.lock and PEP 735 dependency groups. scripts/check.sh already uses uv locally, so this issue finishes the job: local development and CI should resolve and install dependencies the same way. Nothing changes for end users. The package still builds with setuptools and installs from PyPI with plain pip install pythonnative.
Motivation
- Every CI job (ci.yml across three Python versions, docs.yml, e2e.yml across nine device shards, release.yml) reinstalls dependencies from scratch with pip. uv with the official setup action's cache cuts installs to seconds.
- scripts/check.sh promises "if this script is green, CI should be green too," but the script installs with uv while CI installs with pip. One resolver everywhere closes that gap.
- There's no lockfile, and the tool bounds are loose (ruff>=0.5, black>=24.0, and so on), so a new tool release can turn an unrelated PR red. A lockfile makes PR CI deterministic, and dependency upgrades become deliberate.
- The dev and ci extras are identical copy-pasted lists, and dev tooling shouldn't be published as package extras at all. Dependency groups (PEP 735) fix both.
Scope
In scope: pyproject.toml dependency tables, a new uv.lock, the four workflows above, scripts/check.sh, CONTRIBUTING.md, and docs/meta/contributing.md.
Out of scope: the build backend stays setuptools, the user-facing ios and build extras stay extras, and no dependency versions should be upgraded as part of this change (lock whatever currently resolves).
Tasks
-
pyproject.toml
- Replace the dev and ci extras with a single dev entry under [dependency-groups] (the two lists are identical). Move the docs extra to a docs group as well, since it's contributor tooling rather than something users install. Keep ios and build as extras.
- Add the semantic-release lockfile handling described in the release task below.
-
Lockfile: run uv lock and commit uv.lock.
-
Type checking: CI and check.sh currently run mypy --install-types --non-interactive, which has mypy pip-install missing stub packages at run time. That defeats the lockfile (unpinned installs during CI) and also breaks outright in a uv-managed venv, which doesn't include pip. Run mypy locally to see which stubs it wants (likely types-requests), add them to the dev group, and change the mypy invocations to plain uv run mypy without the install flags.
-
.github/workflows/ci.yml: replace actions/setup-python plus pip with astral-sh/setup-uv (enable-cache: true, passing the matrix python-version). Install with uv sync --locked --group dev and run each step through uv run (for example, uv run ruff check .). Replace the pip install . and pip install build / python -m build steps with uv build, which exercises the same packaging path.
-
.github/workflows/docs.yml: install with uv sync --locked --group docs (mkdocstrings needs the project itself importable, which uv sync handles), then uv run mkdocs build --strict.
-
.github/workflows/e2e.yml: the coverage job doesn't install anything and can stay as is. In the e2e-android and e2e-ios jobs, replace pip install -e . with uv sync --locked, and run the suite through uv so the pn CLI is on PATH: for iOS, uv run ./scripts/run-e2e.sh ios ...; for Android, the emulator-runner script becomes bash -lc "uv run ./scripts/run-e2e.sh android ...".
-
.github/workflows/release.yml: add astral-sh/setup-uv, and follow the PSR uv integration guide so the release commit includes a refreshed lockfile:
[tool.semantic_release]
build_command = """
uv lock --upgrade-package pythonnative
git add uv.lock
uv build
"""
PSR stamps the new version into pyproject.toml before running build_command, so uv lock --upgrade-package re-syncs only this project's own version in the lockfile, and the staged uv.lock lands in the release commit. Since build_command now builds the package, the workflow's separate python -m build step and the pip install -U pip build step can go. This repo runs semantic-release directly (not the Docker action), so uv from setup-uv is available on PATH; the pinned python-semantic-release==9.21.2 install can stay as is or move to uv tool install.
-
scripts/check.sh: replace the uv venv --seed and uv pip install -e ".[ci]" build steps with uv sync --locked --group dev (which creates .venv automatically), and replace the python -m build step with uv build. The per-step .venv/bin/python invocations can stay, or switch to uv run.
-
Docs: rewrite the quick start and "Common commands" sections in CONTRIBUTING.md, and the quick start in docs/meta/contributing.md, around uv sync and uv run. Note that uv sync creates and manages .venv automatically, so contributors never need a manual venv or activation step. Remove the remaining pip and .[ci] references.
Acceptance criteria
- uv.lock is committed, and no pip invocations remain in .github/workflows/ or scripts/ (aside from anything PSR requires internally).
- All four workflows pass on the PR.
- From a clean checkout with only uv installed, ./scripts/check.sh passes.
- The contributing docs match the new flow and follow CMOS grammar per AGENTS.md.
Notes
- Suggested PR title: build(pyproject,scripts,workflows): migrate development tooling from pip to uv
- A scheduled job that runs uv lock --upgrade plus the test suite (to catch upstream breakage early, now that PR CI is locked) is deliberately left out; it can be a follow-up.
- The same migration is planned for wybthon in Migrate development tooling and CI from pip to uv wybthon/wybthon#16.
References
Reactions are currently unavailable
Summary
Migrate contributor tooling and CI from pip to uv, with a committed uv.lock and PEP 735 dependency groups. scripts/check.sh already uses uv locally, so this issue finishes the job: local development and CI should resolve and install dependencies the same way. Nothing changes for end users. The package still builds with setuptools and installs from PyPI with plain pip install pythonnative.
Motivation
Scope
In scope: pyproject.toml dependency tables, a new uv.lock, the four workflows above, scripts/check.sh, CONTRIBUTING.md, and docs/meta/contributing.md.
Out of scope: the build backend stays setuptools, the user-facing ios and build extras stay extras, and no dependency versions should be upgraded as part of this change (lock whatever currently resolves).
Tasks
pyproject.toml
Lockfile: run uv lock and commit uv.lock.
Type checking: CI and check.sh currently run mypy --install-types --non-interactive, which has mypy pip-install missing stub packages at run time. That defeats the lockfile (unpinned installs during CI) and also breaks outright in a uv-managed venv, which doesn't include pip. Run mypy locally to see which stubs it wants (likely types-requests), add them to the dev group, and change the mypy invocations to plain uv run mypy without the install flags.
.github/workflows/ci.yml: replace actions/setup-python plus pip with astral-sh/setup-uv (enable-cache: true, passing the matrix python-version). Install with uv sync --locked --group dev and run each step through uv run (for example, uv run ruff check .). Replace the pip install . and pip install build / python -m build steps with uv build, which exercises the same packaging path.
.github/workflows/docs.yml: install with uv sync --locked --group docs (mkdocstrings needs the project itself importable, which uv sync handles), then uv run mkdocs build --strict.
.github/workflows/e2e.yml: the coverage job doesn't install anything and can stay as is. In the e2e-android and e2e-ios jobs, replace pip install -e . with uv sync --locked, and run the suite through uv so the pn CLI is on PATH: for iOS, uv run ./scripts/run-e2e.sh ios ...; for Android, the emulator-runner script becomes bash -lc "uv run ./scripts/run-e2e.sh android ...".
.github/workflows/release.yml: add astral-sh/setup-uv, and follow the PSR uv integration guide so the release commit includes a refreshed lockfile:
PSR stamps the new version into pyproject.toml before running build_command, so uv lock --upgrade-package re-syncs only this project's own version in the lockfile, and the staged uv.lock lands in the release commit. Since build_command now builds the package, the workflow's separate python -m build step and the pip install -U pip build step can go. This repo runs semantic-release directly (not the Docker action), so uv from setup-uv is available on PATH; the pinned python-semantic-release==9.21.2 install can stay as is or move to uv tool install.
scripts/check.sh: replace the uv venv --seed and uv pip install -e ".[ci]" build steps with uv sync --locked --group dev (which creates .venv automatically), and replace the python -m build step with uv build. The per-step .venv/bin/python invocations can stay, or switch to uv run.
Docs: rewrite the quick start and "Common commands" sections in CONTRIBUTING.md, and the quick start in docs/meta/contributing.md, around uv sync and uv run. Note that uv sync creates and manages .venv automatically, so contributors never need a manual venv or activation step. Remove the remaining pip and .[ci] references.
Acceptance criteria
Notes
References