diff --git a/modules/modules.nix b/modules/modules.nix
index aa2c104ca..26d7692f3 100644
--- a/modules/modules.nix
+++ b/modules/modules.nix
@@ -277,6 +277,7 @@ let
./programs/topgrade.nix
./programs/translate-shell.nix
./programs/urxvt.nix
+ ./programs/uv.nix
./programs/vdirsyncer.nix
./programs/vesktop.nix
./programs/vifm.nix
diff --git a/modules/programs/uv.nix b/modules/programs/uv.nix
new file mode 100644
index 000000000..110993fb7
--- /dev/null
+++ b/modules/programs/uv.nix
@@ -0,0 +1,56 @@
+{
+ 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 2e16f93aa..88b7ea2ac 100644
--- a/tests/default.nix
+++ b/tests/default.nix
@@ -466,6 +466,7 @@ 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..9dd99c900
--- /dev/null
+++ b/tests/modules/programs/uv/example-settings.nix
@@ -0,0 +1,29 @@
+{ 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..3539f29db
--- /dev/null
+++ b/tests/modules/programs/uv/no-settings.nix
@@ -0,0 +1,16 @@
+{ ... }:
+{
+ programs.uv = {
+ enable = true;
+ };
+
+ test.stubs.uv = { };
+
+ nmt.script =
+ let
+ expectedConfigPath = "home-files/.config/uv/uv.toml";
+ in
+ ''
+ assertPathNotExists "${expectedConfigPath}"
+ '';
+}