commit 00f8c67f932d85503fac657807f8f7ab9ecb1689 Author: Quirin Kinader Date: Tue Aug 19 23:34:56 2025 +0200 Add python uv example diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..239a8c5 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +*/.venv/ +*.lock diff --git a/python-uv/README.md b/python-uv/README.md new file mode 100644 index 0000000..4a7c155 --- /dev/null +++ b/python-uv/README.md @@ -0,0 +1,5 @@ +# Python environments with uv2nix +Source Documentation: https://pyproject-nix.github.io/uv2nix/usage/hello-world.html + +- To use `uv` to manage virtual environments enter: `nix develop .#impure` +- To use `nix` to manage virtual environments enter: `nix develop .#uv2nix` diff --git a/python-uv/flake.nix b/python-uv/flake.nix new file mode 100644 index 0000000..6e3d977 --- /dev/null +++ b/python-uv/flake.nix @@ -0,0 +1,214 @@ +{ + description = "Hello world flake using uv2nix"; + + inputs = { + nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable"; + + pyproject-nix = { + url = "github:pyproject-nix/pyproject.nix"; + inputs.nixpkgs.follows = "nixpkgs"; + }; + + uv2nix = { + url = "github:pyproject-nix/uv2nix"; + inputs.pyproject-nix.follows = "pyproject-nix"; + inputs.nixpkgs.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 = "nixpkgs"; + }; + }; + + outputs = + { + self, + nixpkgs, + uv2nix, + pyproject-nix, + pyproject-build-systems, + ... + }: + let + inherit (nixpkgs) lib; + + # Load a uv workspace from a workspace root. + # Uv2nix treats all uv projects as workspace projects. + workspace = uv2nix.lib.workspace.loadWorkspace { workspaceRoot = ./.; }; + + # Create package overlay from workspace. + overlay = workspace.mkPyprojectOverlay { + # Prefer prebuilt binary wheels as a package source. + # Sdists are less likely to "just work" because of the metadata missing from uv.lock. + # Binary wheels are more likely to, but may still require overrides for library dependencies. + sourcePreference = "wheel"; # or sourcePreference = "sdist"; + # Optionally customise PEP 508 environment + # environ = { + # platform_release = "5.10.65"; + # }; + }; + + # Extend generated overlay with build fixups + # + # Uv2nix can only work with what it has, and uv.lock is missing essential metadata to perform some builds. + # This is an additional overlay implementing build fixups. + # See: + # - https://pyproject-nix.github.io/uv2nix/FAQ.html + pyprojectOverrides = _final: _prev: { + # Implement build fixups here. + # Note that uv2nix is _not_ using Nixpkgs buildPythonPackage. + # It's using https://pyproject-nix.github.io/pyproject.nix/build.html + }; + + # This example is only using x86_64-linux + pkgs = nixpkgs.legacyPackages.x86_64-linux; + + # Use Python 3.12 from nixpkgs + python = pkgs.python312; + + # Construct package set + pythonSet = + # Use base package set from pyproject.nix builders + (pkgs.callPackage pyproject-nix.build.packages { + inherit python; + }).overrideScope + ( + lib.composeManyExtensions [ + pyproject-build-systems.overlays.default + overlay + pyprojectOverrides + ] + ); + + in + { + # Package a virtual environment as our main application. + # + # Enable no optional dependencies for production build. + packages.x86_64-linux.default = pythonSet.mkVirtualEnv "hello-world-env" workspace.deps.default; + + # Make hello runnable with `nix run` + apps.x86_64-linux = { + default = { + type = "app"; + program = "${self.packages.x86_64-linux.default}/bin/hello"; + }; + }; + + # This example provides two different modes of development: + # - Impurely using uv to manage virtual environments + # - Pure development using uv2nix to manage virtual environments + devShells.x86_64-linux = { + # It is of course perfectly OK to keep using an impure virtualenv workflow and only use uv2nix to build packages. + # This devShell simply adds Python and undoes the dependency leakage done by Nixpkgs Python infrastructure. + impure = pkgs.mkShell { + packages = [ + python + pkgs.uv + ]; + env = + { + # Prevent uv from managing Python downloads + UV_PYTHON_DOWNLOADS = "never"; + # Force uv to use nixpkgs Python interpreter + UV_PYTHON = python.interpreter; + } + // lib.optionalAttrs pkgs.stdenv.isLinux { + # Python libraries often load native shared objects using dlopen(3). + # Setting LD_LIBRARY_PATH makes the dynamic library loader aware of libraries without using RPATH for lookup. + LD_LIBRARY_PATH = lib.makeLibraryPath pkgs.pythonManylinuxPackages.manylinux1; + }; + shellHook = '' + unset PYTHONPATH + ''; + }; + + # This devShell uses uv2nix to construct a virtual environment purely from Nix, using the same dependency specification as the application. + # The notable difference is that we also apply another overlay here enabling editable mode ( https://setuptools.pypa.io/en/latest/userguide/development_mode.html ). + # + # This means that any changes done to your local files do not require a rebuild. + # + # Note: Editable package support is still unstable and subject to change. + uv2nix = + let + # Create an overlay enabling editable mode for all local dependencies. + editableOverlay = workspace.mkEditablePyprojectOverlay { + # Use environment variable + root = "$REPO_ROOT"; + # Optional: Only enable editable for these packages + # members = [ "hello-world" ]; + }; + + # Override previous set with our overrideable overlay. + editablePythonSet = pythonSet.overrideScope ( + lib.composeManyExtensions [ + editableOverlay + + # Apply fixups for building an editable package of your workspace packages + (final: prev: { + hello-world = prev.hello-world.overrideAttrs (old: { + # It's a good idea to filter the sources going into an editable build + # so the editable package doesn't have to be rebuilt on every change. + src = lib.fileset.toSource { + root = old.src; + fileset = lib.fileset.unions [ + (old.src + "/pyproject.toml") + (old.src + "/README.md") + (old.src + "/src/hello_world/__init__.py") + ]; + }; + + # Hatchling (our build system) has a dependency on the `editables` package when building editables. + # + # In normal Python flows this dependency is dynamically handled, and doesn't need to be explicitly declared. + # This behaviour is documented in PEP-660. + # + # With Nix the dependency needs to be explicitly declared. + nativeBuildInputs = + old.nativeBuildInputs + ++ final.resolveBuildSystem { + editables = [ ]; + }; + }); + + }) + ] + ); + + # Build virtual environment, with local packages being editable. + # + # Enable all optional dependencies for development. + virtualenv = editablePythonSet.mkVirtualEnv "hello-world-dev-env" workspace.deps.all; + + in + pkgs.mkShell { + packages = [ + virtualenv + pkgs.uv + ]; + + env = { + # Don't create venv using uv + UV_NO_SYNC = "1"; + + # Force uv to use nixpkgs Python interpreter + UV_PYTHON = python.interpreter; + + # Prevent uv from downloading managed Python's + UV_PYTHON_DOWNLOADS = "never"; + }; + + shellHook = '' + # Undo dependency propagation by nixpkgs. + unset PYTHONPATH + + # Get repository root using git. This is expanded at runtime by the editable `.pth` machinery. + export REPO_ROOT=$(git rev-parse --show-toplevel) + ''; + }; + }; + }; +} diff --git a/python-uv/pyproject.toml b/python-uv/pyproject.toml new file mode 100644 index 0000000..a9ed1da --- /dev/null +++ b/python-uv/pyproject.toml @@ -0,0 +1,22 @@ +[project] +name = "hello-world" +version = "0.1.0" +description = "Add your description here" +readme = "README.md" +requires-python = ">=3.12" +dependencies = [ + "pandas>=2.3.1", + "urllib3>=2.2.3", +] + +[project.scripts] +hello = "hello_world:hello" + +[build-system] +requires = ["hatchling"] +build-backend = "hatchling.build" + +[dependency-groups] +dev = [ + "ruff>=0.6.7", +] diff --git a/python-uv/src/hello_world/__init__.py b/python-uv/src/hello_world/__init__.py new file mode 100644 index 0000000..e69de29