Skip to content

Migrate from poetry to uv#1237

Open
bnmnetp wants to merge 10 commits into
mainfrom
uv-poc-book-server
Open

Migrate from poetry to uv#1237
bnmnetp wants to merge 10 commits into
mainfrom
uv-poc-book-server

Conversation

@bnmnetp

@bnmnetp bnmnetp commented Jun 14, 2026

Copy link
Copy Markdown
Member

Summary

Migrates the monorepo's Python tooling from poetry to uv (Astral). Every service except dash_server is converted; build.py supports both backends so the repo builds throughout the transition.

Model (matches the David Vujic uv-Polylith approach):

  • The root pyproject.toml is the uv development environment only — PEP 621 + hatchling with [tool.hatch.build] dev-mode-dirs, so every rsptx.* brick (plus top-level runestone/development) is importable and live-editable via uv sync.
  • Each 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

uv sync                     # create .venv + install all deps (dev + docs)
uv run build full           # build wheels + images (was: poetry run build full)
uv run pytest               # tests
# uv has no `shell`; use `uv run <cmd>` or `source .venv/bin/activate`

What changed

  • Root → uv dev environment (PEP 621, hatchling, dev-mode-dirs).
  • Services converted to hatchling + hatch-polylith-bricks (poetry's packages = [{include..}][tool.polylith.bricks]): book_server, assignment_server, author_server, admin_server, rsmanage, w2p_login_assign_grade.
  • interactives: JS-release-only — converted for the runestone script/version/dev, but no Python wheel is built (build.py skips it; its webpack pre-build still runs).
  • build.py: builds each project with the right tool by its declared build-backend (uv build for hatchling, poetry build-project for the legacy path); fixed the root version read ([project].version) and version bump (uv version).
  • CI (test-crud.yml): astral-sh/setup-uv (cache keyed on uv.lock), uv sync, uv run pytest.
  • Docs: poetry.rstuv.rst plus building_servers / database / addbook / debugging / environmental_vars / structure / tutorial / left_to_merge updated; tutorial now teaches [tool.polylith.bricks]. sample.env notes .env is auto-loaded by uv 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/buildptx console scripts run, and pytest collects 90 tests.

Gotchas worth knowing (documented in the commits)

  • migrate-to-uv's generated [tool.hatch...include/sources] with ../../components paths silently builds an empty wheel — stock hatchling can't reach outside the project root, so projects must use hatch-polylith-bricks.
  • Poetry exclude globs "**/dir/*" don't prune dirs under the polylith-bricks hook (Python fnmatch); use the directory form "**/dir".
  • Poetry license = "" / "GPL" are invalid PEP 621 SPDX — dropped.
  • PEP 508 marker is sys_platform, not poetry's sys.platform.
  • uv resolves a newer black (25.12.0) that reformatted 2 vendored pylti1p3 files the old venv left alone (one commit).

Not in scope / follow-ups

  • dash_server is intentionally NOT converted — left on poetry; build.py's dual-backend support keeps building it. Poetry must stay installed until it's done.
  • The tutorial's new-project flow was converted (commands + [tool.polylith.bricks] format) but not validated end-to-end (didn't run poly create under uv).
  • CLAUDE.md is gitignored, so its local uv updates are not in this PR.

🤖 Generated with Claude Code

bnmnetp and others added 8 commits June 13, 2026 16:56
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>
Copilot AI review requested due to automatic review settings June 14, 2026 12:59

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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"]
Comment thread docs/source/database.rst
~~~~~~~~~~~~~~~~~~~

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.
bnmnetp and others added 2 commits June 14, 2026 08:05
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>
Copilot AI review requested due to automatic review settings June 14, 2026 13:51

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 26 out of 35 changed files in this pull request and generated 1 comment.

Comment thread docs/source/database.rst
~~~~~~~~~~~~~~~~~~~

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.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants