Migrate dependency management to uv
This issue tracks consolidating williaby/ledgerbase from its current
triple-state dependency management (Poetry + generated requirements.txt +
uv-already-invoked-in-CI) down to a single uv source of truth: a committed
uv.lock plus a PEP 621 [project] table and PEP 735 [dependency-groups].
This is a planning issue. Execution must follow the local clone, branch,
signed commit, PR workflow (the repo enforces signed commits and branch
protection; the GitHub Contents API cannot be used for commits).
1. Current state (verified)
The repo is in the messiest possible dual/triple state:
pyproject.toml uses the legacy [tool.poetry] format only. There is NO
PEP 621 [project] table and NO [dependency-groups].
poetry.lock is committed (lock metadata python-versions = ">=3.10").
poetry.toml configures an in-project .venv and no-root = true.
requirements.txt and dev-requirements.txt are committed and are
GENERATED artifacts: generate_requirements.sh runs
poetry export --without-hashes to produce them, and
prepare-poetry.yml regenerates dev-requirements.txt the same way.
uv is ALREADY invoked in three CI workflows against a repo that has no
uv project layout:
.github/workflows/codeql.yml: uv sync --no-dev
.github/workflows/fips-compatibility.yml: uv sync --frozen (also
uv python install 3.12, uv run ...)
.github/workflows/slsa-provenance.yml: uv build
.github/workflows/ci.yml delegates to the org reusable
ByronWilliamsCPA/.github/.github/workflows/python-ci.yml, which expects a
uv project layout (a committed uv.lock).
Dockerfile installs via Poetry (COPY pyproject.toml poetry.lock,
poetry install --no-root, CMD ["poetry", "run", "flask", ...]).
- Pre-commit (
.pre-commit-config.yaml) drives every local gate through
poetry run ... (ruff, nox sessions for mypy/bandit/semgrep/vulture,
pip-audit).
noxfile.py is large (about 49 KB) and is the local task runner; nox is
also a runtime dependency in pyproject.
- No
renovate.json / .github/renovate.json and no dependabot.yml
present, so there is no bot manager config to reconcile right now.
2. Which mechanism is authoritative today
Poetry is authoritative. requirements.txt and dev-requirements.txt
are derived from poetry.lock via poetry export. The uv references in CI
are aspirational and currently cannot succeed: uv sync requires a PEP 621
[project] table or a committed uv.lock, and the repo has neither. Those
three uv jobs (codeql, fips-compatibility, slsa-provenance) are therefore
either failing or not resolving the intended dependency set. This is the
single most important correctness reason to finish the migration rather than
leave it half-done.
3. Version drift between the three mechanisms (DO NOT assume they agree)
The committed requirements.txt does NOT match the committed poetry.lock.
Confirmed conflicts (poetry.lock value vs requirements.txt value):
| Package |
poetry.lock |
requirements.txt |
Conflict |
| cryptography |
44.0.2 |
46.0.7 |
YES, major drift |
| requests |
2.32.3 |
2.33.0 |
YES |
| marshmallow |
3.26.1 |
3.26.2 |
YES |
| python-dotenv |
1.1.0 |
1.2.2 |
YES |
| packaging |
(pin per lock) |
24.2 |
check during reconcile |
requirements.txt also carries packages not in the Poetry main group
(for example peewee, full opentelemetry-* stack), indicating it was
generated at a different time or with a different resolution than the current
poetry.lock. Treat requirements.txt as STALE. The reconciliation step
below resolves which versions win; do not blindly trust either file.
Additional inconsistency: pyproject.toml declares
python = ">=3.11,<4.0", but poetry.lock metadata says
python-versions = ">=3.10". The house standard is requires-python = ">=3.10".
This three-way Python-version disagreement must be resolved during migration.
4. App vs package; target Python
- Type: deployable application (Flask web app), but it DOES ship an
importable package. src/ledgerbase/ is a real package
(__init__.py, wsgi.py, config.py, etc.) and [tool.poetry] declares
packages = [{ include = "ledgerbase", from = "src" }]. There are also
sibling source trees: src/cli, src/etl, src/flask, src/schema,
src/scripts, src/services.
- Because it is run as an app (gunicorn /
flask run in the Dockerfile) and
is not published as a consumed library, a build backend is only needed if a
wheel is actually built. NOTE: slsa-provenance.yml currently runs
uv build, which DOES require a build backend. Decision required: either
keep building a wheel (add hatchling as the build backend per house
standard, replacing poetry-core) or drop uv build from the SLSA
workflow if no wheel is consumed. Recommendation: keep hatchling so the
existing SLSA provenance flow and release.yml continue to function.
- Target Python: set
requires-python = ">=3.10" per house standard.
This widens the current >=3.11 floor; confirm nothing in the codebase
uses 3.11-only syntax before committing the lower floor. If 3.11 features
are in use, document the deviation and keep >=3.11.
5. Dependencies that complicate uv lock
- Assured OSS private index.
pyproject.toml declares
[[tool.poetry.source]] for assured-oss
(https://us-python.pkg.dev/cloud-aoss/cloud-aoss-python/simple) as the
PRIMARY source with PyPI supplemental, and CI authenticates via
keyrings.google-artifactregistry-auth and GCP_SA_JSON. uv has no
[tool.poetry.source]; this must be re-expressed as
[[tool.uv.index]] (with default/priority semantics) plus credential
handling through UV_INDEX_* env vars or keyring. This is the single
biggest migration risk: a misconfigured index makes uv lock resolve from
the wrong registry or fail to authenticate.
- Native / C-extension deps:
cryptography, psycopg[binary] (extras),
ruamel-yaml-clib, greenlet. uv resolves wheels fine, but the platform
markers in the current requirements.txt (aarch64/x86_64, CPython-only,
win32 tzdata) must be allowed to regenerate naturally under uv lock; do
not hand-port them.
- Heavy / unusual deps:
semgrep (large, pulls many transitive deps),
plaid-python (pinned 30.0.0), keyring + the Google artifact registry
keyring backend.
- Tooling-as-runtime smell:
nox is listed under
[tool.poetry.dependencies] (runtime) AND [tool.poetry.group.dev].
During migration, nox belongs in the dev dependency group only (or
stays a system tool), not in [project.dependencies].
6. CI, pre-commit, and container surfaces that must switch to uv
| Surface |
Current |
Target |
ci.yml -> org python-ci.yml |
expects uv.lock |
provide committed uv.lock |
codeql.yml |
uv sync --no-dev (broken, no lock) |
works once uv.lock exists |
fips-compatibility.yml |
uv sync --frozen (broken, no lock) |
works once uv.lock exists |
slsa-provenance.yml |
uv build |
keep; requires hatchling build backend |
prepare-poetry.yml |
installs Poetry, poetry export |
remove or replace with a uv setup |
security-pip-audit.yml |
audits requirements.txt + dev-requirements.txt |
audit via uv export or uv pip compile output, or pip-audit against the uv env |
other poetry-referencing workflows (gh-pages, license, release, sbom, security-semgrep/snyk/trivy, weekly-check, template workflows) |
poetry ... |
migrate each to uv sync / uv run |
.pre-commit-config.yaml |
every hook uses poetry run ... |
switch to uv run ... |
Dockerfile |
Poetry install + poetry run flask |
uv sync --frozen + uv run (or gunicorn entrypoint) |
Makefile |
poetry hint in setup target |
update to uv |
generate_requirements.sh |
poetry export |
delete (uv.lock replaces it) |
7. Monorepo / workspace
Single pyproject.toml at root. Multiple source trees live under src/
(ledgerbase, cli, etl, flask, schema, schema, services,
scripts), but they are one project, not separate distributions. No uv
workspace needed unless these are later split. Flag for confirmation during
execution.
8. What is missing for full uv adoption
- A PEP 621
[project] table (name, version, requires-python,
dependencies).
- A
[dependency-groups] table with a dev group (PEP 735).
- A committed
uv.lock.
- uv-native index config (
[[tool.uv.index]]) replacing
[[tool.poetry.source]], with Assured OSS auth wired through uv.
- A chosen build backend (
hatchling) if uv build / wheel publishing is
retained.
- Removal of
[tool.poetry], poetry.toml, poetry.lock,
requirements.txt, dev-requirements.txt, generate_requirements.sh,
and prepare-poetry.yml once uv.lock is the source of truth.
9. Gotchas to honor during execution
- Renovate: none configured today. If a
renovate.json is added later,
the Python manager MUST be "pep621", never "uv" (Renovate silently
rejects the whole config otherwise). Run renovate-config-validator after
any change.
- Signed commits + branch protection: executor must use local clone,
branch, signed commit, PR. No Contents API commits.
- AOSS auth in CI: the migration must keep Assured OSS reachable for
uv lock / uv sync in CI, or resolution will fall back to PyPI or fail.
Ordered execution checklist
- Read
ByronWilliamsCPA/cookiecutter-python-template/pyproject.toml as the
target layout reference.
- Add a PEP 621
[project] table: name, version,
requires-python = ">=3.10" (confirm no 3.11-only syntax first), and move
the current [tool.poetry.dependencies] runtime deps into
[project.dependencies] (drop nox from runtime).
- Add
[dependency-groups] with a dev group containing the current
[tool.poetry.group.dev.dependencies].
- Replace
[[tool.poetry.source]] with [[tool.uv.index]] for Assured OSS
(primary) and PyPI, and wire credentials via UV_INDEX_* / keyring.
- Set the build backend:
[build-system] requires = ["hatchling"],
build-backend = "hatchling.build", plus [tool.hatch.build.targets.wheel]
packages = ["src/ledgerbase"] (retain uv build / SLSA flow).
- Remove all
[tool.poetry*] tables and poetry.toml.
- Reconcile version drift: run
uv lock and treat its resolution as the new
truth; explicitly review the cryptography 44 vs 46, requests, marshmallow,
python-dotenv, packaging conflicts and pin intentionally where needed.
- Commit the generated
uv.lock.
- Migrate
.pre-commit-config.yaml hooks from poetry run to uv run.
- Migrate
Dockerfile to uv sync --frozen + uv run (or gunicorn).
- Migrate every poetry-referencing workflow to
uv; delete
prepare-poetry.yml; update security-pip-audit.yml to audit the uv
environment.
- Verify
codeql.yml, fips-compatibility.yml, slsa-provenance.yml, and
the org python-ci.yml path all pass with the committed uv.lock.
- Delete
requirements.txt, dev-requirements.txt, generate_requirements.sh,
and poetry.lock.
- Update
Makefile setup hint and README.md install docs to uv.
- Run
pre-commit run --all-files; open a signed-commit PR.
Migrate dependency management to uv
This issue tracks consolidating
williaby/ledgerbasefrom its currenttriple-state dependency management (Poetry + generated
requirements.txt+uv-already-invoked-in-CI) down to a single
uvsource of truth: a committeduv.lockplus a PEP 621[project]table and PEP 735[dependency-groups].This is a planning issue. Execution must follow the local clone, branch,
signed commit, PR workflow (the repo enforces signed commits and branch
protection; the GitHub Contents API cannot be used for commits).
1. Current state (verified)
The repo is in the messiest possible dual/triple state:
pyproject.tomluses the legacy[tool.poetry]format only. There is NOPEP 621
[project]table and NO[dependency-groups].poetry.lockis committed (lock metadatapython-versions = ">=3.10").poetry.tomlconfigures an in-project.venvandno-root = true.requirements.txtanddev-requirements.txtare committed and areGENERATED artifacts:
generate_requirements.shrunspoetry export --without-hashesto produce them, andprepare-poetry.ymlregeneratesdev-requirements.txtthe same way.uvis ALREADY invoked in three CI workflows against a repo that has nouv project layout:
.github/workflows/codeql.yml:uv sync --no-dev.github/workflows/fips-compatibility.yml:uv sync --frozen(alsouv python install 3.12,uv run ...).github/workflows/slsa-provenance.yml:uv build.github/workflows/ci.ymldelegates to the org reusableByronWilliamsCPA/.github/.github/workflows/python-ci.yml, which expects auv project layout (a committed
uv.lock).Dockerfileinstalls via Poetry (COPY pyproject.toml poetry.lock,poetry install --no-root,CMD ["poetry", "run", "flask", ...])..pre-commit-config.yaml) drives every local gate throughpoetry run ...(ruff, nox sessions for mypy/bandit/semgrep/vulture,pip-audit).
noxfile.pyis large (about 49 KB) and is the local task runner; nox isalso a runtime dependency in pyproject.
renovate.json/.github/renovate.jsonand nodependabot.ymlpresent, so there is no bot manager config to reconcile right now.
2. Which mechanism is authoritative today
Poetry is authoritative.
requirements.txtanddev-requirements.txtare derived from
poetry.lockviapoetry export. The uv references in CIare aspirational and currently cannot succeed:
uv syncrequires a PEP 621[project]table or a committeduv.lock, and the repo has neither. Thosethree uv jobs (codeql, fips-compatibility, slsa-provenance) are therefore
either failing or not resolving the intended dependency set. This is the
single most important correctness reason to finish the migration rather than
leave it half-done.
3. Version drift between the three mechanisms (DO NOT assume they agree)
The committed
requirements.txtdoes NOT match the committedpoetry.lock.Confirmed conflicts (poetry.lock value vs requirements.txt value):
requirements.txtalso carries packages not in the Poetry main group(for example
peewee, fullopentelemetry-*stack), indicating it wasgenerated at a different time or with a different resolution than the current
poetry.lock. Treatrequirements.txtas STALE. The reconciliation stepbelow resolves which versions win; do not blindly trust either file.
Additional inconsistency:
pyproject.tomldeclarespython = ">=3.11,<4.0", butpoetry.lockmetadata sayspython-versions = ">=3.10". The house standard isrequires-python = ">=3.10".This three-way Python-version disagreement must be resolved during migration.
4. App vs package; target Python
importable package.
src/ledgerbase/is a real package(
__init__.py,wsgi.py,config.py, etc.) and[tool.poetry]declarespackages = [{ include = "ledgerbase", from = "src" }]. There are alsosibling source trees:
src/cli,src/etl,src/flask,src/schema,src/scripts,src/services.flask runin the Dockerfile) andis not published as a consumed library, a build backend is only needed if a
wheel is actually built. NOTE:
slsa-provenance.ymlcurrently runsuv build, which DOES require a build backend. Decision required: eitherkeep building a wheel (add
hatchlingas the build backend per housestandard, replacing
poetry-core) or dropuv buildfrom the SLSAworkflow if no wheel is consumed. Recommendation: keep
hatchlingso theexisting SLSA provenance flow and
release.ymlcontinue to function.requires-python = ">=3.10"per house standard.This widens the current
>=3.11floor; confirm nothing in the codebaseuses 3.11-only syntax before committing the lower floor. If 3.11 features
are in use, document the deviation and keep
>=3.11.5. Dependencies that complicate
uv lockpyproject.tomldeclares[[tool.poetry.source]]forassured-oss(
https://us-python.pkg.dev/cloud-aoss/cloud-aoss-python/simple) as thePRIMARY source with PyPI supplemental, and CI authenticates via
keyrings.google-artifactregistry-authandGCP_SA_JSON. uv has no[tool.poetry.source]; this must be re-expressed as[[tool.uv.index]](withdefault/priority semantics) plus credentialhandling through
UV_INDEX_*env vars or keyring. This is the singlebiggest migration risk: a misconfigured index makes
uv lockresolve fromthe wrong registry or fail to authenticate.
cryptography,psycopg[binary](extras),ruamel-yaml-clib,greenlet. uv resolves wheels fine, but the platformmarkers in the current
requirements.txt(aarch64/x86_64, CPython-only,win32 tzdata) must be allowed to regenerate naturally under
uv lock; donot hand-port them.
semgrep(large, pulls many transitive deps),plaid-python(pinned30.0.0),keyring+ the Google artifact registrykeyring backend.
noxis listed under[tool.poetry.dependencies](runtime) AND[tool.poetry.group.dev].During migration,
noxbelongs in thedevdependency group only (orstays a system tool), not in
[project.dependencies].6. CI, pre-commit, and container surfaces that must switch to uv
ci.yml-> orgpython-ci.ymluv.lockcodeql.ymluv sync --no-dev(broken, no lock)uv.lockexistsfips-compatibility.ymluv sync --frozen(broken, no lock)uv.lockexistsslsa-provenance.ymluv buildhatchlingbuild backendprepare-poetry.ymlpoetry exportsecurity-pip-audit.ymlrequirements.txt+dev-requirements.txtuv exportoruv pip compileoutput, orpip-auditagainst the uv envgh-pages,license,release,sbom,security-semgrep/snyk/trivy,weekly-check, template workflows)poetry ...uv sync/uv run.pre-commit-config.yamlpoetry run ...uv run ...Dockerfilepoetry run flaskuv sync --frozen+uv run(orgunicornentrypoint)Makefilegenerate_requirements.shpoetry export7. Monorepo / workspace
Single
pyproject.tomlat root. Multiple source trees live undersrc/(
ledgerbase,cli,etl,flask,schema,schema,services,scripts), but they are one project, not separate distributions. No uvworkspace needed unless these are later split. Flag for confirmation during
execution.
8. What is missing for full uv adoption
[project]table (name, version,requires-python,dependencies).[dependency-groups]table with adevgroup (PEP 735).uv.lock.[[tool.uv.index]]) replacing[[tool.poetry.source]], with Assured OSS auth wired through uv.hatchling) ifuv build/ wheel publishing isretained.
[tool.poetry],poetry.toml,poetry.lock,requirements.txt,dev-requirements.txt,generate_requirements.sh,and
prepare-poetry.ymlonce uv.lock is the source of truth.9. Gotchas to honor during execution
renovate.jsonis added later,the Python manager MUST be
"pep621", never"uv"(Renovate silentlyrejects the whole config otherwise). Run
renovate-config-validatorafterany change.
branch, signed commit, PR. No Contents API commits.
uv lock/uv syncin CI, or resolution will fall back to PyPI or fail.Ordered execution checklist
ByronWilliamsCPA/cookiecutter-python-template/pyproject.tomlas thetarget layout reference.
[project]table:name,version,requires-python = ">=3.10"(confirm no 3.11-only syntax first), and movethe current
[tool.poetry.dependencies]runtime deps into[project.dependencies](dropnoxfrom runtime).[dependency-groups]with adevgroup containing the current[tool.poetry.group.dev.dependencies].[[tool.poetry.source]]with[[tool.uv.index]]for Assured OSS(primary) and PyPI, and wire credentials via
UV_INDEX_*/ keyring.[build-system] requires = ["hatchling"],build-backend = "hatchling.build", plus[tool.hatch.build.targets.wheel]packages = ["src/ledgerbase"](retainuv build/ SLSA flow).[tool.poetry*]tables andpoetry.toml.uv lockand treat its resolution as the newtruth; explicitly review the cryptography 44 vs 46, requests, marshmallow,
python-dotenv, packaging conflicts and pin intentionally where needed.
uv.lock..pre-commit-config.yamlhooks frompoetry runtouv run.Dockerfiletouv sync --frozen+uv run(or gunicorn).uv; deleteprepare-poetry.yml; updatesecurity-pip-audit.ymlto audit the uvenvironment.
codeql.yml,fips-compatibility.yml,slsa-provenance.yml, andthe org
python-ci.ymlpath all pass with the committeduv.lock.requirements.txt,dev-requirements.txt,generate_requirements.sh,and
poetry.lock.Makefilesetup hint andREADME.mdinstall docs to uv.pre-commit run --all-files; open a signed-commit PR.