packaging: Switch from poetry to uv#146
Merged
myme merged 11 commits intonovem-code:mainfrom Dec 15, 2025
Merged
Conversation
sondove
approved these changes
Dec 15, 2025
Contributor
sondove
left a comment
There was a problem hiding this comment.
Magic, got it.
Does this mean nix workflows are back on the table?
Collaborator
Author
Yes. Last commit enables it again. |
bjornars
approved these changes
Dec 15, 2025
Contributor
bjornars
left a comment
There was a problem hiding this comment.
Magic!
My only gripe is that I think this is a complete "relock" of the dependencies - so we've implicitly upgraded all kinds of minor versions. If it works, it's probably ok?
My experiments led me to uvx migrate-to-uv which migrated poetry.lock file over - but maybe that introduces other problems, idk.
Collaborator
Author
Collaborator
Author
|
Here's the #!/usr/bin/env python3
"""Compare package versions between uv.lock and poetry.lock (from main branch)."""
import re
import subprocess
import sys
from collections import defaultdict
def parse_uv_lock(filepath: str) -> dict[str, set[str]]:
"""Parse uv.lock file and extract package names and versions."""
packages = defaultdict(set)
with open(filepath) as f:
content = f.read()
# uv.lock format: [[package]] blocks with name = "..." and version = "..."
# Split by [[package]] markers
package_blocks = re.split(r'\[\[package\]\]', content)
for block in package_blocks[1:]: # Skip the header before first package
name_match = re.search(r'^name\s*=\s*"([^"]+)"', block, re.MULTILINE)
version_match = re.search(r'^version\s*=\s*"([^"]+)"', block, re.MULTILINE)
if name_match and version_match:
name = name_match.group(1)
version = version_match.group(1)
packages[name].add(version)
return packages
def parse_poetry_lock(content: str) -> dict[str, set[str]]:
"""Parse poetry.lock content and extract package names and versions."""
packages = defaultdict(set)
# poetry.lock format: [[package]] blocks with name = "..." and version = "..."
package_blocks = re.split(r'\[\[package\]\]', content)
for block in package_blocks[1:]: # Skip the header before first package
name_match = re.search(r'^name\s*=\s*"([^"]+)"', block, re.MULTILINE)
version_match = re.search(r'^version\s*=\s*"([^"]+)"', block, re.MULTILINE)
if name_match and version_match:
name = name_match.group(1)
version = version_match.group(1)
packages[name].add(version)
return packages
def get_poetry_lock_from_main() -> str:
"""Get poetry.lock content from the main branch."""
result = subprocess.run(
['git', 'show', 'main:poetry.lock'],
capture_output=True,
text=True
)
if result.returncode != 0:
print(f"Error fetching poetry.lock from main: {result.stderr}", file=sys.stderr)
sys.exit(1)
return result.stdout
def main():
# Parse both lock files
uv_packages = parse_uv_lock('uv.lock')
poetry_content = get_poetry_lock_from_main()
poetry_packages = parse_poetry_lock(poetry_content)
# Normalize package names (replace - with _ for comparison)
def normalize(name: str) -> str:
return name.lower().replace('-', '_')
uv_normalized = {normalize(k): (k, v) for k, v in uv_packages.items()}
poetry_normalized = {normalize(k): (k, v) for k, v in poetry_packages.items()}
all_packages = sorted(set(uv_normalized.keys()) | set(poetry_normalized.keys()))
# Categorize differences
only_in_uv = []
only_in_poetry = []
version_diff = []
same_version = []
for norm_name in all_packages:
in_uv = norm_name in uv_normalized
in_poetry = norm_name in poetry_normalized
if in_uv and not in_poetry:
name, versions = uv_normalized[norm_name]
only_in_uv.append((name, versions))
elif in_poetry and not in_uv:
name, versions = poetry_normalized[norm_name]
only_in_poetry.append((name, versions))
else:
uv_name, uv_versions = uv_normalized[norm_name]
poetry_name, poetry_versions = poetry_normalized[norm_name]
if uv_versions != poetry_versions:
version_diff.append((uv_name, poetry_versions, uv_versions))
else:
same_version.append((uv_name, uv_versions))
# Print results
print("=" * 70)
print("PACKAGE VERSION COMPARISON: poetry.lock (main) vs uv.lock (current)")
print("=" * 70)
if version_diff:
print(f"\n### VERSION DIFFERENCES ({len(version_diff)} packages)")
print("-" * 70)
print(f"{'Package':<35} {'poetry.lock':<15} {'uv.lock':<15}")
print("-" * 70)
for name, poetry_vers, uv_vers in sorted(version_diff):
poetry_str = ', '.join(sorted(poetry_vers))
uv_str = ', '.join(sorted(uv_vers))
print(f"{name:<35} {poetry_str:<15} {uv_str:<15}")
if only_in_uv:
print(f"\n### ONLY IN uv.lock ({len(only_in_uv)} packages)")
print("-" * 70)
for name, versions in sorted(only_in_uv):
ver_str = ', '.join(sorted(versions))
print(f" {name}: {ver_str}")
if only_in_poetry:
print(f"\n### ONLY IN poetry.lock ({len(only_in_poetry)} packages)")
print("-" * 70)
for name, versions in sorted(only_in_poetry):
ver_str = ', '.join(sorted(versions))
print(f" {name}: {ver_str}")
if same_version:
print(f"\n### SAME VERSION ({len(same_version)} packages)")
print("-" * 70)
for name, versions in sorted(same_version):
ver_str = ', '.join(sorted(versions))
print(f" {name}: {ver_str}")
# Summary
print("\n" + "=" * 70)
print("SUMMARY")
print("=" * 70)
print(f" Total packages in poetry.lock (main): {len(poetry_packages)}")
print(f" Total packages in uv.lock (current): {len(uv_packages)}")
print(f" Packages with version differences: {len(version_diff)}")
print(f" Packages only in uv.lock: {len(only_in_uv)}")
print(f" Packages only in poetry.lock: {len(only_in_poetry)}")
print(f" Packages with same version: {len(same_version)}")
if __name__ == '__main__':
main() |
sondove
approved these changes
Dec 15, 2025
Contributor
sondove
left a comment
There was a problem hiding this comment.
Seems pretty solid to me
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.

Description
This PR switches
novem-pythonover to useuvfor development and packaging. This change is motivated bypoetry2nixbeing abandoned in favor ofuv2nix. That said,uvhas been gaining popularity as a faster and "better" package manager for Python outside of thenixecosystem.Installation
uvinstallation instructions can be found here: https://docs.astral.sh/uv/#installationExamples
Python 3.9:
2025-12-15-155800.webm
Python 3.13:
2025-12-15-160005.webm
Pre-commit hooks:
CI workflow:
Nix check: