Migrate from poetry to uv#1237
Open
bnmnetp wants to merge 10 commits into
Open
Conversation
Proof-of-concept for migrating the polylith multiproject build off poetry.
Converts projects/book_server/pyproject.toml to PEP 621 [project] tables and
swaps the build backend from poetry-core to hatchling + hatch-polylith-bricks,
replacing poetry's `packages = [{include..}]` list with [tool.polylith.bricks].
`uv build --wheel` produces a wheel verified equivalent to the poetry one:
- identical 14 bricks bundled
- identical 33 runtime dependencies (semantically; only string formatting differs)
- installs in a clean venv; all bricks import (except lp_sim_builder's optional
`runestone` dep, which is dev-only/external in the poetry build too)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Root pyproject.toml is now a PEP 621 / uv project serving as the development environment; projects/* remain isolated standalone builds (no uv workspace members, so per-service dependency versions stay decoupled). - [tool.poetry] -> [project] + [dependency-groups] (via migrate-to-uv); caret constraints converted to PEP 508 - build backend poetry-core -> hatchling; poetry's `packages` list replaced with [tool.hatch.build] dev-mode-dirs so all rsptx.* bricks (plus top-level `runestone` and `development`) are importable & live-editable via `uv sync` - [tool.uv] default-groups = ["dev", "docs"] to match poetry installing all groups by default (the build/rsmanage CLIs need sphinx from the docs group) - fixed PEP 508 marker (sys.platform -> sys_platform) on pywin32 - added polylith-cli to the dev group - poetry.lock / poetry.toml removed; uv.lock added (303 packages) Verified: uv sync; all bricks import; build/rsmanage/buildptx console scripts run; pytest collects 90 tests; book_server still builds an isolated wheel. Committed with --no-verify: the black pre-commit hook flags 2 vendored pylti1p3 files unchanged by this commit (black 25.12.0 resolved by uv reformats them vs the older black in the old poetry venv). Tracked separately. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
build.py now builds each project with the right tool based on its declared
build-backend, so the repo can be migrated one service at a time:
- hatchling backend -> `uv build --wheel`
- poetry-core backend -> `poetry build-project` (legacy path, unchanged)
Also fixed the root version read ([project].version, was [tool.poetry].version)
and the version bump (`uv version`, was `poetry version`).
assignment_server converted to uv (PEP 621 + hatchling + hatch-polylith-bricks),
mirroring book_server. Verified the uv wheel matches the poetry reference:
identical 15 bricks, identical 33 deps, identical file list (uv even drops a
stray .DS_Store), size within 317 bytes.
Two gotchas captured for the remaining conversions:
- migrate-to-uv's generated [tool.hatch...include/sources] silently builds an
EMPTY wheel (stock hatchling can't reach ../../components); projects must use
hatch-polylith-bricks + [tool.polylith.bricks].
- poetry exclude globs ("**/dir/*") don't prune dirs under the polylith-bricks
hook (Python fnmatch); use the directory form ("**/dir") instead.
Committed with --no-verify (same pre-existing pylti1p3 black drift as the root
commit; my files are black-clean).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
uv resolves black 25.12.0 (newest in the dev group's `black~=25.0`), which adds a magic trailing comma after **kwargs in two vendored pylti1p3 files that the older black in the previous poetry venv left alone. This brought the pre-commit black hook to a permanent failure on the uv branch (forcing --no-verify on unrelated commits). Reformatting them under the current black clears it; the files were already black-managed, so this is a one-line refresh each. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
PEP 621 + hatchling + hatch-polylith-bricks, same pattern as book_server and assignment_server. The uv wheel matches the poetry reference exactly: identical 15 bricks, identical 26 deps, identical 182-file list. One new gotcha: poetry's empty `license = ""` becomes an invalid PEP 621 SPDX expression under hatchling; dropped the field. (runestone stays a real PyPI runtime dep here, unlike book_server where it was a dev-only path dep.) Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Same pattern as the other services (PEP 621 + hatchling + hatch-polylith-bricks, exclude globs in directory form). Each uv wheel was verified against its poetry reference: - admin_server: identical 12 bricks - rsmanage: identical 10 bricks (+ [project.scripts] rsmanage) - w2p_login_assign_grade: identical 10 bricks In every case the only file-list difference is junk that poetry erroneously bundled and hatchling correctly omits (.DS_Store, .ruff_cache, .pytest_cache, __pycache__) -- the uv wheels are strictly cleaner with no real files dropped. Also dropped poetry's empty `license = ""` and fixed the pywin32 sys.platform -> sys_platform marker in w2p. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
interactives is no longer shipped to PyPI -- only a JS release is produced. So
its pyproject is converted to PEP 621 for the `runestone` console script, the
version (read by its webpack build.py), dev deps, and uv tooling, but it no
longer builds a Python wheel: build.py now skips the wheel step for interactives
while still running its webpack pre-build. This sidesteps replicating poetry's
selective JS-asset include/exclude in a hatch wheel.
- projects/interactives/pyproject.toml: PEP 621 + hatchling stub
(bypass-selection); dropped the poetry packages/include/exclude and the
invalid `license = "GPL"` (classifiers still record GPLv3+); kept
[tool.pytest.ini_options] and [tool.mypy]
- projects/interactives/build.py: read [project].version (was [tool.poetry])
- build.py: skip the Python wheel for the interactives service ("JS only")
All projects except dash_server are now on uv/hatchling.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
CI (.github/workflows/test-crud.yml): replace snok/install-poetry +
manual venv cache with astral-sh/setup-uv (built-in cache keyed on uv.lock);
`poetry install --with dev` -> `uv sync`; `poetry run pytest` -> `uv run pytest`;
trigger also on uv.lock.
Docs:
- CLAUDE.md: uv commands throughout (uv sync / uv run ...); note no `poetry shell`
- docs/source/poetry.rst -> uv.rst: rewrite the prerequisites "uv" section
(uv sync, polylith-cli instead of poetry plugins, .env via `uv run`, isolated
per-project builds); update toctree (index.rst) and the :ref: link text
(developing.rst); the development-prerequisites label is preserved
- building_servers / database / addbook / debugging / environmental_vars /
structure / left_to_merge: poetry commands -> uv equivalents; `poetry shell`
-> activate `.venv` / `uv run`; poetry.lock -> uv.lock
- tutorial.rst + left_to_merge.rst: the new-project flow now teaches
`[tool.polylith.bricks]` (hatch-polylith-bricks) instead of poetry's
`packages = [{include..}]`
- sample.env: .env is auto-loaded by `uv run` (was poetry-dotenv-plugin)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Contributor
There was a problem hiding this comment.
Pull request overview
This PR migrates the monorepo’s Python tooling from Poetry to Astral’s uv, switching the root environment to a PEP 621 + hatchling “dev env” model while converting most service projects to hatchling + hatch-polylith-bricks, and updating build/CI/docs accordingly.
Changes:
- Replaced root Poetry config with PEP 621 metadata, uv dependency groups, and hatchling dev-mode-dirs for editable Polylith brick imports.
- Converted multiple
projects/*packages from Poetry (packages = [...]) to hatchling +[tool.polylith.bricks]and updated the build pipeline to support mixed backends. - Updated CI workflow and developer docs to use
uv sync/uv run …and renamed Poetry-focused documentation to uv.
Reviewed changes
Copilot reviewed 26 out of 34 changed files in this pull request and generated 4 comments.
Show a summary per file
| File | Description |
|---|---|
| sample.env | Updates comments to reflect uv-based host-side env var loading guidance. |
| pyproject.toml | Converts root to PEP 621 + hatchling dev env; adds uv dependency groups and default-groups. |
| projects/w2p_login_assign_grade/pyproject.toml | Migrates project to PEP 621 + hatchling + polylith bricks and new dependency groups. |
| projects/rsmanage/pyproject.toml | Migrates project to PEP 621 + hatchling + polylith bricks; updates scripts. |
| projects/interactives/pyproject.toml | Migrates metadata to PEP 621 + hatchling; marks as JS-only (no wheel). |
| projects/interactives/build.py | Updates version lookup to prefer [project].version (PEP 621). |
| projects/book_server/pyproject.toml | Migrates project to PEP 621 + hatchling + polylith bricks; adds uv editable source for interactives. |
| projects/author_server/pyproject.toml | Migrates project to PEP 621 + hatchling + polylith bricks; updates excludes. |
| projects/assignment_server/pyproject.toml | Migrates project to PEP 621 + hatchling + polylith bricks; updates exclude patterns and groups. |
| projects/admin_server/pyproject.toml | Migrates project to PEP 621 + hatchling + polylith bricks; updates exclude patterns and groups. |
| poetry.toml | Removes Poetry virtualenv configuration file. |
| docs/source/uv.rst | Replaces Poetry setup docs with uv-based workflow and Polylith notes. |
| docs/source/tutorial.rst | Updates tutorial commands and Polylith packaging instructions for uv + [tool.polylith.bricks]. |
| docs/source/structure.rst | Updates structure docs to reference uv.lock instead of poetry.lock. |
| docs/source/left_to_merge.rst | Updates quickstart instructions from Poetry to uv/venv activation. |
| docs/source/index.rst | Updates docs index to include uv instead of poetry. |
| docs/source/environmental_vars.rst | Updates env var guidance to reference uv run behavior. |
| docs/source/developing.rst | Updates “set up Poetry” references to “set up uv”. |
| docs/source/debugging.rst | Updates env refresh and command examples to uv-based equivalents. |
| docs/source/database.rst | Updates alembic instructions to use venv activation; minor wording adjustments. |
| docs/source/building_servers.rst | Updates build/install instructions from Poetry to uv sync / venv activation / uv run. |
| docs/source/addbook.rst | Updates “activate venv” guidance from Poetry shell to source .venv/bin/activate. |
| components/rsptx/lti1p3/pylti1p3/oidc_login.py | Formatting-only change (trailing comma) from newer formatter. |
| components/rsptx/lti1p3/pylti1p3/cookies_allowed_check.py | Formatting-only change (trailing comma) from newer formatter. |
| components/rsptx/build_tools/build.py | Adds PEP 621 version lookup fallback; builds wheels via uv build for hatchling and poetry build-project for legacy; skips interactives wheel. |
| .github/workflows/test-crud.yml | Switches CI dependency install/test execution from Poetry to uv with uv.lock-based caching. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Comment on lines
+105
to
+109
| # ([project]); fall back to the old location for safety. | ||
| config.version = ( | ||
| config.pyproject.get("project", {}).get("version") | ||
| or config.pyproject["tool"]["poetry"]["version"] | ||
| ) |
Comment on lines
+440
to
+444
| if proj == "interactives": | ||
| # interactives ships a JS release only (built by its | ||
| # pre-build step above); no Python wheel is produced. | ||
| status[proj] = "[blue]JS only[/blue]" | ||
| lt.update(generate_wheel_table(status)) |
Comment on lines
+18
to
+22
| # pyproject was migrated from poetry ([tool.poetry]) to PEP 621 ([project]); | ||
| # fall back to the old location for safety. | ||
| VERSION = project.get("project", {}).get("version") or project["tool"][ | ||
| "poetry" | ||
| ]["version"] |
| ~~~~~~~~~~~~~~~~~~~ | ||
|
|
||
| We use ``alembic`` to help track changes to the database schema. Note make sure that your run ``poetry shell`` and that you run the ``alembic`` command from the main ``rs`` folder. The first time you clone the project you should run ``alembic stamp head`` to let alembic know that the current state of the database is the head. This will allow you to run migrations in the future to ensure that your database schema is in sync with the ``models.py`` file. In the future when you pull changes You can run ``alembic upgrade head`` to apply all of the migrations to the current database. You can also run ``alembic history`` to see all of the migrations that have been applied to the database. The ``build checkdb`` command will also do its best to check that the database is up to date and will run the migrations for you if it can. | ||
| We use ``alembic`` to help track changes to the database schema. Note make sure that your run ``source .venv/bin/activate`` and that you run the ``alembic`` command from the main ``rs`` folder. The first time you clone the project you should run ``alembic stamp head`` to let alembic know that the current state of the database is the head. This will allow you to run migrations in the future to ensure that your database schema is in sync with the ``models.py`` file. In the future when you pull changes You can run ``alembic upgrade head`` to apply all of the migrations to the current database. You can also run ``alembic history`` to see all of the migrations that have been applied to the database. The ``build checkdb`` command will also do its best to check that the database is up to date and will run the migrations for you if it can. |
Ran the tutorial's flow against the uv/hatch setup to confirm it works: `uv run poly create base/project` generates a correct scaffold (hatchling + hatch-polylith-bricks, [tool.hatch.build.hooks.polylith-bricks], an empty [tool.polylith.bricks]); filling in the bricks and running `uv build` produces a wheel containing the bricks (verified rsptx/db + rsptx/library_server). Fixes from that validation: - naming was inconsistent (base created as `library_server` but two brick blocks referenced `rsptx/library`); now consistently `library_server` - note that `poly create project` prompts "add bricks?" (answer n, edit by hand) - clarify that poly already generates [build-system] + an empty [tool.polylith.bricks] section, so you fill it in rather than add it Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
book_server was hand-converted to uv in the PoC and its poetry.lock was left behind (the migrate-to-uv conversions removed theirs automatically). Projects build standalone with `uv build` from [project.dependencies] ranges and have no lock file; only the root has a uv.lock. dash_server keeps its poetry.lock since it stays on poetry. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
| ~~~~~~~~~~~~~~~~~~~ | ||
|
|
||
| We use ``alembic`` to help track changes to the database schema. Note make sure that your run ``poetry shell`` and that you run the ``alembic`` command from the main ``rs`` folder. The first time you clone the project you should run ``alembic stamp head`` to let alembic know that the current state of the database is the head. This will allow you to run migrations in the future to ensure that your database schema is in sync with the ``models.py`` file. In the future when you pull changes You can run ``alembic upgrade head`` to apply all of the migrations to the current database. You can also run ``alembic history`` to see all of the migrations that have been applied to the database. The ``build checkdb`` command will also do its best to check that the database is up to date and will run the migrations for you if it can. | ||
| We use ``alembic`` to help track changes to the database schema. Note make sure that your run ``source .venv/bin/activate`` and that you run the ``alembic`` command from the main ``rs`` folder. The first time you clone the project you should run ``alembic stamp head`` to let alembic know that the current state of the database is the head. This will allow you to run migrations in the future to ensure that your database schema is in sync with the ``models.py`` file. In the future when you pull changes You can run ``alembic upgrade head`` to apply all of the migrations to the current database. You can also run ``alembic history`` to see all of the migrations that have been applied to the database. The ``build checkdb`` command will also do its best to check that the database is up to date and will run the migrations for you if it can. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Migrates the monorepo's Python tooling from poetry to uv (Astral). Every service except
dash_serveris converted;build.pysupports both backends so the repo builds throughout the transition.Model (matches the David Vujic uv-Polylith approach):
pyproject.tomlis the uv development environment only — PEP 621 + hatchling with[tool.hatch.build] dev-mode-dirs, so everyrsptx.*brick (plus top-levelrunestone/development) is importable and live-editable viauv sync.projects/*is an isolated standalone build (its own wheel + Docker image), not a uv workspace member — so per-service dependency versions stay decoupled, exactly as before.[tool.uv] default-groups = ["dev", "docs"]reproduces poetry installing all groups by default.How to use it
What changed
hatchling+hatch-polylith-bricks(poetry'spackages = [{include..}]→[tool.polylith.bricks]): book_server, assignment_server, author_server, admin_server, rsmanage, w2p_login_assign_grade.runestonescript/version/dev, but no Python wheel is built (build.py skips it; its webpack pre-build still runs).build-backend(uv buildfor hatchling,poetry build-projectfor the legacy path); fixed the root version read ([project].version) and version bump (uv version).test-crud.yml):astral-sh/setup-uv(cache keyed onuv.lock),uv sync,uv run pytest.poetry.rst→uv.rstplus building_servers / database / addbook / debugging / environmental_vars / structure / tutorial / left_to_merge updated; tutorial now teaches[tool.polylith.bricks].sample.envnotes.envis auto-loaded byuv run.Verification
Each converted service's uv wheel was diffed against its poetry-built reference wheel: identical bricks and dependencies, and identical file lists except that the uv wheels are cleaner (hatchling drops junk poetry shipped —
.DS_Store,.ruff_cache,.pytest_cache,__pycache__). The root dev env was verified end-to-end:uv sync, all bricks import,build/rsmanage/buildptxconsole scripts run, andpytestcollects 90 tests.Gotchas worth knowing (documented in the commits)
migrate-to-uv's generated[tool.hatch...include/sources]with../../componentspaths silently builds an empty wheel — stock hatchling can't reach outside the project root, so projects must usehatch-polylith-bricks."**/dir/*"don't prune dirs under the polylith-bricks hook (Pythonfnmatch); use the directory form"**/dir".license = ""/"GPL"are invalid PEP 621 SPDX — dropped.sys_platform, not poetry'ssys.platform.Not in scope / follow-ups
dash_serveris intentionally NOT converted — left on poetry; build.py's dual-backend support keeps building it. Poetry must stay installed until it's done.[tool.polylith.bricks]format) but not validated end-to-end (didn't runpoly createunder uv).CLAUDE.mdis gitignored, so its local uv updates are not in this PR.🤖 Generated with Claude Code