Skip to content

Migrate from poetry to uv #22

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 4 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 10 additions & 10 deletions .github/workflows/test-pr.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,19 +14,19 @@ jobs:
- name: 'Check out code'
uses: actions/checkout@v3
- name: 'Install Python'
uses: actions/setup-python@v4
uses: actions/setup-python@v5
with:
python-version: '3.10'
- name: 'Install uv'
uses: astral-sh/setup-uv@v5
- name: 'Install cookiecutter'
run: pip install cookiecutter
- name: 'Install Poetry'
run: curl -sSL https://install.python-poetry.org | python3 - --version 1.3.2
- name: 'Generate template'
run: cookiecutter --no-input . project_name='Test Project'
- name: 'Generate lock file'
run: poetry -C ./test-project lock
run: uv --project ./test-project lock
- name: 'Cache generated project'
uses: actions/cache@v3
uses: actions/cache@v4
with:
path: ./test-project
key: test-project-${{ github.run_id }}
Expand All @@ -37,16 +37,16 @@ jobs:
runs-on: ubuntu-latest
strategy:
matrix:
python-version: ['3.10', '3.11']
python-version: ['3.10', '3.11', '3.12', '3.13']
steps:
- name: 'Install Python'
uses: actions/setup-python@v4
uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}
- name: 'Install Poetry'
run: curl -sSL https://install.python-poetry.org | python3 - --version 1.3.2
- name: 'Install uv'
uses: astral-sh/setup-uv@v5
- name: 'Restore generated project'
uses: actions/cache@v3
uses: actions/cache@v4
with:
path: ./test-project
key: test-project-${{ github.run_id }}
Expand Down
70 changes: 39 additions & 31 deletions {{cookiecutter.project_slug}}/Makefile
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
POETRY := poetry
POETRY_RUN := $(POETRY) run
UV := uv
UV_RUN := $(UV) run --


default: check test-unit
Expand All @@ -13,11 +13,7 @@ clean:

.PHONY: build
build:
$(POETRY) build

.PHONY: poetry-install
poetry-install:
$(POETRY) install
$(uv) build


# Tests
Expand All @@ -26,14 +22,17 @@ TEST_ARGS :=

test: test-all

test-all: poetry-install
$(POETRY_RUN) pytest src/tests --maxfail=1 --verbose --durations=0 --numprocesses=4 --dist=worksteal $(TEST_ARGS)
.PHONY: test-all
test-all:
$(UV_RUN) pytest src/tests --maxfail=1 --verbose --durations=0 --numprocesses=4 --dist=worksteal $(TEST_ARGS)

test-unit: poetry-install
$(POETRY_RUN) pytest src/tests/unit --maxfail=1 --verbose $(TEST_ARGS)
.PHONY: test-unit
test-unit:
$(UV_RUN) pytest src/tests/unit --maxfail=1 --verbose $(TEST_ARGS)

test-integration: poetry-install
$(POETRY_RUN) pytest src/tests/integration --maxfail=1 --verbose --durations=0 --numprocesses=4 --dist=worksteal $(TEST_ARGS)
.PHONY: test-integration
test-integration:
$(UV_RUN) pytest src/tests/integration --maxfail=1 --verbose --durations=0 --numprocesses=4 --dist=worksteal $(TEST_ARGS)


# Coverage
Expand All @@ -59,34 +58,43 @@ cov-integration: test-integration
format: autoflake isort black
check: check-flake8 check-mypy check-autoflake check-isort check-black

check-flake8: poetry-install
$(POETRY_RUN) flake8 src
.PHONY: check-flake8
check-flake8:
$(UV_RUN) flake8 src

check-mypy: poetry-install
$(POETRY_RUN) mypy src
.PHONY: check-mypy
check-mypy:
$(UV_RUN) mypy src

autoflake: poetry-install
$(POETRY_RUN) autoflake --quiet --in-place src
.PHONY: autoflake
autoflake:
$(UV_RUN) autoflake --quiet --in-place src

check-autoflake: poetry-install
$(POETRY_RUN) autoflake --quiet --check src
.PHONY: check-autoflake
check-autoflake:
$(UV_RUN) autoflake --quiet --check src

isort: poetry-install
$(POETRY_RUN) isort src
.PHONY: isort
isort:
$(UV_RUN) isort src

check-isort: poetry-install
$(POETRY_RUN) isort --check src
.PHONY: check-isort
check-isort:
$(UV_RUN) isort --check src

black: poetry-install
$(POETRY_RUN) black src
.PHONY: black
black:
$(UV_RUN) black src

check-black: poetry-install
$(POETRY_RUN) black --check src
.PHONY: check-black
check-black:
$(UV_RUN) black --check src


# Optional tools

SRC_FILES := $(shell find src -type f -name '*.py')

pyupgrade: poetry-install
$(POETRY_RUN) pyupgrade --py310-plus $(SRC_FILES)
.PHONY: pyupgrade
pyupgrade:
$(UV_RUN) pyupgrade --py310-plus $(SRC_FILES)
5 changes: 2 additions & 3 deletions {{cookiecutter.project_slug}}/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

## Installation

Prerequsites: `python >= 3.10`, `pip >= 20.0.2`, `poetry >= 1.3.2`.
Prerequsites: `python >= 3.10`, [`uv`](https://docs.astral.sh/uv/).

```bash
make build
Expand All @@ -19,5 +19,4 @@ Use `make` to run common tasks (see the [Makefile](Makefile) for a complete list
* `make check`: Check code style
* `make format`: Format code
* `make test-unit`: Run unit tests

For interactive use, spawn a shell with `poetry shell` (after `poetry install`), then run an interpreter.
* `make test-integration`: Run integration tests
95 changes: 68 additions & 27 deletions {{cookiecutter.project_slug}}/flake.nix
Original file line number Diff line number Diff line change
@@ -1,41 +1,82 @@
{
description = "{{ cookiecutter.project_slug }} - {{ cookiecutter.description }}";
inputs = {
nixpkgs.url = "nixpkgs/nixos-22.05";
nixpkgs.url = "nixpkgs/nixos-unstable";
flake-utils.url = "github:numtide/flake-utils";
poetry2nix.url = "github:nix-community/poetry2nix";
pyproject-nix = {
url = "github:pyproject-nix/pyproject.nix";
inputs.nixpkgs.follows = "uv2nix/nixpkgs";
# inputs.uv2nix.follows = "nixpkgs";
};
pyproject-build-systems = {
url = "github:pyproject-nix/build-system-pkgs";
inputs.pyproject-nix.follows = "pyproject-nix";
inputs.uv2nix.follows = "uv2nix";
inputs.nixpkgs.follows = "uv2nix/nixpkgs";
# inputs.uv2nix.follows = "nixpkgs";
};
uv2nix = {
url = "github:pyproject-nix/uv2nix";
inputs.pyproject-nix.follows = "pyproject-nix";
# stale nixpkgs is missing the alias `lib.match` -> `builtins.match`
# therefore point uv2nix to a patched nixpkgs, which introduces this alias
# this is a temporary solution until nixpkgs us up-to-date again
inputs.nixpkgs.url = "github:runtimeverification/nixpkgs/libmatch";
# inputs.uv2nix.follows = "nixpkgs";
};
};
outputs = { self, nixpkgs, flake-utils, poetry2nix }:
let
allOverlays = [
poetry2nix.overlay
(final: prev: {
{{ cookiecutter.project_slug }} = prev.poetry2nix.mkPoetryApplication {
python = prev.python310;
projectDir = ./.;
groups = [];
# We remove `dev` from `checkGroups`, so that poetry2nix does not try to resolve dev dependencies.
checkGroups = [];
};
})
];
in flake-utils.lib.eachSystem [
outputs = { self, nixpkgs, flake-utils, pyproject-nix, pyproject-build-systems, uv2nix }:
let
pythonVer = "310";
in flake-utils.lib.eachSystem [
"x86_64-linux"
"x86_64-darwin"
"aarch64-linux"
"aarch64-darwin"
] (system:
let
pkgs = import nixpkgs {
inherit system;
overlays = allOverlays;
let
# due to the nixpkgs that we use in this flake being outdated, uv is also heavily outdated
# we can instead use the binary release of uv provided by uv2nix for now
uvOverlay = final: prev: {
uv = uv2nix.packages.${final.system}.uv-bin;
};
{{ cookiecutter.project_slug }}Overlay = final: prev: {
{{ cookiecutter.project_slug }} = final.callPackage ./nix/{{ cookiecutter.project_slug }} {
inherit pyproject-nix pyproject-build-systems uv2nix;
python = final."python${pythonVer}";
};
in {
packages = rec {
inherit (pkgs) {{ cookiecutter.project_slug }};
default = {{ cookiecutter.project_slug }};
};
pkgs = import nixpkgs {
inherit system;
overlays = [
uvOverlay
{{ cookiecutter.project_slug }}Overlay
];
};
python = pkgs."python${pythonVer}";
in {
devShells.default = pkgs.mkShell {
name = "uv develop shell";
buildInputs = [
python
pkgs.uv
];
env = {
# prevent uv from managing Python downloads and force use of specific
UV_PYTHON_DOWNLOADS = "never";
UV_PYTHON = python.interpreter;
};
}) // {
overlay = nixpkgs.lib.composeManyExtensions allOverlays;
shellHook = ''
unset PYTHONPATH
'';
};
packages = rec {
inherit (pkgs) {{ cookiecutter.project_slug }};
default = {{ cookiecutter.project_slug }};
};
}) // {
overlays.default = final: prev: {
inherit (self.packages.${final.system}) {{ cookiecutter.project_slug }};
};
};
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
final: prev:
let
inherit (final) resolveBuildSystem;
inherit (builtins) mapAttrs;

# Build system dependencies specified in the shape expected by resolveBuildSystem
# The empty lists below are lists of optional dependencies.
#
# A package `foo` with specification written as:
# `setuptools-scm[toml]` in pyproject.toml would be written as
# `foo.setuptools-scm = [ "toml" ]` in Nix
buildSystemOverrides = {
# add dependencies here, e.g.:
# pyperclip.setuptools = [ ];
};
in
mapAttrs (
name: spec:
prev.${name}.overrideAttrs (old: {
nativeBuildInputs = old.nativeBuildInputs ++ resolveBuildSystem spec;
})
) buildSystemOverrides
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
{
lib,
callPackage,

pyproject-nix,
pyproject-build-systems,
uv2nix,

python
}:
let
pyproject-util = callPackage pyproject-nix.build.util {};
pyproject-packages = callPackage pyproject-nix.build.packages {
inherit python;
};

# load a uv workspace from a workspace root
workspace = uv2nix.lib.workspace.loadWorkspace {
workspaceRoot = ../..;
};

# create overlay
lockFileOverlay = workspace.mkPyprojectOverlay {
# prefer "wheel" over "sdist" due to maintance overhead
# there is no bundled set of overlays for "sdist" in uv2nix, in contrast to poetry2nix
sourcePreference = "wheel";
};

buildSystemsOverlay = import ./build-systems-overlay.nix;

# construct package set
pythonSet = pyproject-packages.overrideScope (lib.composeManyExtensions [
# make build tools available by default as these are not necessarily specified in python lock files
pyproject-build-systems.overlays.default
# include all packages from the python lock file
lockFileOverlay
# add build system overrides to certain python packages
buildSystemsOverlay
]);
in pyproject-util.mkApplication {
# default dependancy group enables no optional dependencies and no dependency-groups
venv = pythonSet.mkVirtualEnv "{{ cookiecutter.project_slug }}-env" workspace.deps.default;
package = pythonSet.{{ cookiecutter.project_slug }};
}
Loading
Loading