Skip to content
Merged
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)
4 changes: 2 additions & 2 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,5 @@ 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
* `make test-integration`: Run integration tests

For interactive use, spawn a shell with `poetry shell` (after `poetry install`), then run an interpreter.
87 changes: 60 additions & 27 deletions {{cookiecutter.project_slug}}/flake.nix
Original file line number Diff line number Diff line change
@@ -1,41 +1,74 @@
{
description = "{{ cookiecutter.project_slug }} - {{ cookiecutter.description }}";
inputs = {
nixpkgs.url = "nixpkgs/nixos-22.05";
nixpkgs.url = "nixpkgs/nixos-25.05";
flake-utils.url = "github:numtide/flake-utils";
poetry2nix.url = "github:nix-community/poetry2nix";
uv2nix.url = "github:pyproject-nix/uv2nix/680e2f8e637bc79b84268949d2f2b2f5e5f1d81c";
# 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
uv2nix.inputs.nixpkgs.url = "github:runtimeverification/nixpkgs/libmatch";
# inputs.nixpkgs.follows = "nixpkgs";
pyproject-build-systems.url = "github:pyproject-nix/build-system-pkgs/7dba6dbc73120e15b558754c26024f6c93015dd7";
pyproject-build-systems = {
inputs.nixpkgs.follows = "uv2nix/nixpkgs";
inputs.uv2nix.follows = "uv2nix";
inputs.pyproject-nix.follows = "uv2nix/pyproject-nix";
};
pyproject-nix.follows = "uv2nix/pyproject-nix";
};
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 }};
};
};
}
3 changes: 3 additions & 0 deletions {{cookiecutter.project_slug}}/nix/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
## Notes
#### git submodules
If you use git submodules that are not required for building the project with `nix`, then you should add these directories to the `gitignoreSourcePure` list in `nix/{{ cookiecutter.project_slug }}/default.nix`, see the respective left-over `TODO` in the nix file. Otherwise, the nix build that is uploaded to the nix binary cache by CI might not match the version that is requested by `kup`, in case this project is offered as a package by `kup`. This is due to weird behaviour in regards to git submodules by `git` and `nix`, where a submodule directory might exist and be empty or instead not exist, which impacts the resulting nix hash, which is of impartance when offering/downloading cached nix derivations. See [runtimeverification/k/pull/4804](https://github.com/runtimeverification/k/pull/4804) for more information.
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,55 @@
{
lib,
callPackage,
nix-gitignore,

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 = lib.cleanSource (nix-gitignore.gitignoreSourcePure [
../../.gitignore
".github/"
"result*"
# do not include submodule directories that might be initilized empty or non-existent due to nix/git
# otherwise cachix build might not match the version that is requested by `kup`
# TODO: for new projects, add your submodule directories that are not required for nix builds here!
# e.g., `"/docs/external-computation"` with `external-computation` being a git submodule directory
# "/docs/external-computation"
] ../..
);
};

# 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