diff --git a/modules/modules.nix b/modules/modules.nix
index 75f8ac461..256f53bf9 100644
--- a/modules/modules.nix
+++ b/modules/modules.nix
@@ -244,6 +244,7 @@ let
./programs/topgrade.nix
./programs/translate-shell.nix
./programs/urxvt.nix
+ ./programs/uv.nix
./programs/vdirsyncer.nix
./programs/vifm.nix
./programs/vim-vint.nix
diff --git a/modules/programs/uv.nix b/modules/programs/uv.nix
new file mode 100644
index 000000000..f7efa7a09
--- /dev/null
+++ b/modules/programs/uv.nix
@@ -0,0 +1,45 @@
+{ pkgs, config, lib, ... }:
+
+let
+
+ inherit (lib) mkEnableOption mkPackageOption mkOption literalExpression;
+
+ tomlFormat = pkgs.formats.toml { };
+ cfg = config.programs.uv;
+
+in {
+ meta.maintainers = with lib.maintainers; [ mirkolenz ];
+
+ options.programs.uv = {
+ enable = mkEnableOption "uv";
+
+ package = mkPackageOption pkgs "uv" { };
+
+ settings = mkOption {
+ type = tomlFormat.type;
+ default = { };
+ example = literalExpression ''
+ {
+ python-downloads = "never";
+ python-preference = "only-system";
+ pip.index-url = "https://test.pypi.org/simple";
+ }
+ '';
+ description = ''
+ Configuration written to
+ {file}`$XDG_CONFIG_HOME/uv/uv.toml`.
+ See
+ and
+ for more information.
+ '';
+ };
+ };
+
+ config = lib.mkIf cfg.enable {
+ home.packages = [ cfg.package ];
+
+ xdg.configFile."uv/uv.toml" = lib.mkIf (cfg.settings != { }) {
+ source = tomlFormat.generate "uv-config" cfg.settings;
+ };
+ };
+}
diff --git a/tests/default.nix b/tests/default.nix
index 2eaaaa436..68e1ef002 100644
--- a/tests/default.nix
+++ b/tests/default.nix
@@ -157,6 +157,7 @@ in import nmtSrc {
./modules/programs/tmux
./modules/programs/topgrade
./modules/programs/translate-shell
+ ./modules/programs/uv
./modules/programs/vifm
./modules/programs/vim-vint
./modules/programs/vscode
diff --git a/tests/modules/programs/uv/default.nix b/tests/modules/programs/uv/default.nix
new file mode 100644
index 000000000..c37bf75fc
--- /dev/null
+++ b/tests/modules/programs/uv/default.nix
@@ -0,0 +1,4 @@
+{
+ uv-example-settings = ./example-settings.nix;
+ uv-no-settings = ./no-settings.nix;
+}
diff --git a/tests/modules/programs/uv/example-settings.nix b/tests/modules/programs/uv/example-settings.nix
new file mode 100644
index 000000000..23ec2f9e4
--- /dev/null
+++ b/tests/modules/programs/uv/example-settings.nix
@@ -0,0 +1,27 @@
+{ pkgs, ... }:
+
+{
+ programs.uv = {
+ enable = true;
+ settings = {
+ python-downloads = "never";
+ python-preference = "only-system";
+ pip.index-url = "https://test.pypi.org/simple";
+ };
+ };
+
+ test.stubs.uv = { };
+
+ nmt.script = let
+ expectedConfigPath = "home-files/.config/uv/uv.toml";
+ expectedConfigContent = pkgs.writeText "uv.config-custom.expected" ''
+ python-downloads = "never"
+ python-preference = "only-system"
+ [pip]
+ index-url = "https://test.pypi.org/simple"
+ '';
+ in ''
+ assertFileExists "${expectedConfigPath}"
+ assertFileContent "${expectedConfigPath}" "${expectedConfigContent}"
+ '';
+}
diff --git a/tests/modules/programs/uv/no-settings.nix b/tests/modules/programs/uv/no-settings.nix
new file mode 100644
index 000000000..5357d1cb1
--- /dev/null
+++ b/tests/modules/programs/uv/no-settings.nix
@@ -0,0 +1,10 @@
+{ ... }: {
+ programs.uv = { enable = true; };
+
+ test.stubs.uv = { };
+
+ nmt.script = let expectedConfigPath = "home-files/.config/uv/uv.toml";
+ in ''
+ assertPathNotExists "${expectedConfigPath}"
+ '';
+}