env-module-manager 0.3.0__tar.gz → 0.4.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (35) hide show
  1. {env_module_manager-0.3.0 → env_module_manager-0.4.0}/AGENTS.md +3 -0
  2. {env_module_manager-0.3.0 → env_module_manager-0.4.0}/PKG-INFO +17 -10
  3. {env_module_manager-0.3.0 → env_module_manager-0.4.0}/README.md +16 -9
  4. {env_module_manager-0.3.0 → env_module_manager-0.4.0}/docs/collective-environments.md +1 -0
  5. {env_module_manager-0.3.0 → env_module_manager-0.4.0}/docs/configuration.md +5 -0
  6. {env_module_manager-0.3.0 → env_module_manager-0.4.0}/docs/installation.md +9 -3
  7. {env_module_manager-0.3.0 → env_module_manager-0.4.0}/docs/python-tools.md +23 -0
  8. {env_module_manager-0.3.0 → env_module_manager-0.4.0}/docs/reference.md +1 -0
  9. {env_module_manager-0.3.0 → env_module_manager-0.4.0}/module_manager/__init__.py +1 -1
  10. {env_module_manager-0.3.0 → env_module_manager-0.4.0}/module_manager/cli.py +85 -9
  11. {env_module_manager-0.3.0 → env_module_manager-0.4.0}/module_manager/config.py +4 -0
  12. {env_module_manager-0.3.0 → env_module_manager-0.4.0}/module_manager/deploy.py +26 -4
  13. {env_module_manager-0.3.0 → env_module_manager-0.4.0}/pyproject.toml +1 -1
  14. {env_module_manager-0.3.0 → env_module_manager-0.4.0}/tests/test_cli.py +7 -0
  15. {env_module_manager-0.3.0 → env_module_manager-0.4.0}/tests/test_config.py +2 -0
  16. {env_module_manager-0.3.0 → env_module_manager-0.4.0}/tests/test_modulefile.py +27 -1
  17. {env_module_manager-0.3.0 → env_module_manager-0.4.0}/uv.lock +1 -1
  18. {env_module_manager-0.3.0 → env_module_manager-0.4.0}/.github/workflows/publish.yml +0 -0
  19. {env_module_manager-0.3.0 → env_module_manager-0.4.0}/.github/workflows/tag.yml +0 -0
  20. {env_module_manager-0.3.0 → env_module_manager-0.4.0}/.github/workflows/test.yml +0 -0
  21. {env_module_manager-0.3.0 → env_module_manager-0.4.0}/.gitignore +0 -0
  22. {env_module_manager-0.3.0 → env_module_manager-0.4.0}/.python-version +0 -0
  23. {env_module_manager-0.3.0 → env_module_manager-0.4.0}/.readthedocs.yaml +0 -0
  24. {env_module_manager-0.3.0 → env_module_manager-0.4.0}/CONTRIBUTING.md +0 -0
  25. {env_module_manager-0.3.0 → env_module_manager-0.4.0}/LICENSE.md +0 -0
  26. {env_module_manager-0.3.0 → env_module_manager-0.4.0}/docs/development.md +0 -0
  27. {env_module_manager-0.3.0 → env_module_manager-0.4.0}/docs/environment-modules.md +0 -0
  28. {env_module_manager-0.3.0 → env_module_manager-0.4.0}/docs/index.md +0 -0
  29. {env_module_manager-0.3.0 → env_module_manager-0.4.0}/docs/rust-tools.md +0 -0
  30. {env_module_manager-0.3.0 → env_module_manager-0.4.0}/docs/shell-tools.md +0 -0
  31. {env_module_manager-0.3.0 → env_module_manager-0.4.0}/docs/stylesheets/extra.css +0 -0
  32. {env_module_manager-0.3.0 → env_module_manager-0.4.0}/docs/uninstall.md +0 -0
  33. {env_module_manager-0.3.0 → env_module_manager-0.4.0}/example.bash +0 -0
  34. {env_module_manager-0.3.0 → env_module_manager-0.4.0}/mkdocs.yml +0 -0
  35. {env_module_manager-0.3.0 → env_module_manager-0.4.0}/module_manager/modulefile.py +0 -0
@@ -90,6 +90,9 @@ Common settings:
90
90
  * `module_root` or `MODULE_MANAGER_MODULE_ROOT`
91
91
  * `[python].indexes`, `MODULE_MANAGER_INDEX`, or `MODULE_MANAGER_INDEXES`
92
92
  * `[python].find_links` or `MODULE_MANAGER_FIND_LINKS`
93
+ * `[python].uv_config_file` for the uv `--config-file` option. Do not add a
94
+ `MODULE_MANAGER_*` wrapper for this; users should use uv's `UV_CONFIG_FILE`
95
+ environment variable when they want environment-based uv configuration.
93
96
 
94
97
  ## Documentation
95
98
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: env-module-manager
3
- Version: 0.3.0
3
+ Version: 0.4.0
4
4
  Summary: Deploy Python, Rust, shell, and collective CLI environments behind GNU environment modulefiles
5
5
  Project-URL: Homepage, https://github.com/ejb90/module-manager
6
6
  Project-URL: Documentation, https://module-manager.readthedocs.io/
@@ -50,6 +50,20 @@ It writes tools and modulefiles into predictable versioned locations:
50
50
  /prod/modulefiles/<name>/<version>
51
51
  ```
52
52
 
53
+ ## Installation
54
+
55
+ Install from PyPI:
56
+
57
+ ```sh
58
+ pip install env-module-manager
59
+ ```
60
+
61
+ Or install as a uv-managed tool:
62
+
63
+ ```sh
64
+ uv tool install env-module-manager
65
+ ```
66
+
53
67
  ## Quick Start
54
68
 
55
69
  Deploy a Python CLI with `uv tool install`:
@@ -108,12 +122,5 @@ module-manager uninstall ruff 0.8.0 \
108
122
 
109
123
  ## Documentation
110
124
 
111
- Full documentation lives in `docs/` and can be served with MkDocs:
112
-
113
- ```sh
114
- uv run mkdocs serve
115
- ```
116
-
117
- Start with `docs/index.md` for installation, configuration, Python tools, Rust
118
- tools, shell scripts, collective environments, uninstall, and local development
119
- notes.
125
+ Read the full documentation on
126
+ [Read the Docs](https://module-manager.readthedocs.io/).
@@ -18,6 +18,20 @@ It writes tools and modulefiles into predictable versioned locations:
18
18
  /prod/modulefiles/<name>/<version>
19
19
  ```
20
20
 
21
+ ## Installation
22
+
23
+ Install from PyPI:
24
+
25
+ ```sh
26
+ pip install env-module-manager
27
+ ```
28
+
29
+ Or install as a uv-managed tool:
30
+
31
+ ```sh
32
+ uv tool install env-module-manager
33
+ ```
34
+
21
35
  ## Quick Start
22
36
 
23
37
  Deploy a Python CLI with `uv tool install`:
@@ -76,12 +90,5 @@ module-manager uninstall ruff 0.8.0 \
76
90
 
77
91
  ## Documentation
78
92
 
79
- Full documentation lives in `docs/` and can be served with MkDocs:
80
-
81
- ```sh
82
- uv run mkdocs serve
83
- ```
84
-
85
- Start with `docs/index.md` for installation, configuration, Python tools, Rust
86
- tools, shell scripts, collective environments, uninstall, and local development
87
- notes.
93
+ Read the full documentation on
94
+ [Read the Docs](https://module-manager.readthedocs.io/).
@@ -22,6 +22,7 @@ package = "ruff==0.8.0"
22
22
  python = "3.12"
23
23
  indexes = ["https://packages.example/simple"]
24
24
  find_links = ["/prod/wheels"]
25
+ uv_config_file = "/prod/config/uv.toml"
25
26
 
26
27
  [[tools]]
27
28
  type = "rust"
@@ -17,6 +17,7 @@ module_root = "/prod/modulefiles"
17
17
  [python]
18
18
  indexes = ["https://packages.example/simple"]
19
19
  find_links = ["/prod/wheels"]
20
+ uv_config_file = "/prod/config/uv.toml"
20
21
  ```
21
22
 
22
23
  Use a different config file with `--config`:
@@ -36,6 +37,10 @@ export MODULE_MANAGER_FIND_LINKS=/prod/wheels,/prod/more-wheels
36
37
 
37
38
  `MODULE_MANAGER_CONFIG` can point to an alternate TOML config file.
38
39
 
40
+ There is no `MODULE_MANAGER_*` environment variable for uv configuration files.
41
+ Use uv's own `UV_CONFIG_FILE` environment variable when you want to set that via
42
+ the environment.
43
+
39
44
  ## CLI Precedence
40
45
 
41
46
  CLI options override both environment variables and TOML config:
@@ -38,18 +38,24 @@ in every new shell.
38
38
 
39
39
  ## Install module-manager
40
40
 
41
- From a local checkout:
41
+ From PyPI with pip:
42
42
 
43
43
  ```sh
44
- uv tool install .
44
+ pip install env-module-manager
45
45
  ```
46
46
 
47
- From PyPI:
47
+ As a uv-managed tool:
48
48
 
49
49
  ```sh
50
50
  uv tool install env-module-manager
51
51
  ```
52
52
 
53
+ From a local checkout:
54
+
55
+ ```sh
56
+ uv tool install .
57
+ ```
58
+
53
59
  During development, use:
54
60
 
55
61
  ```sh
@@ -51,6 +51,29 @@ module-manager deploy-python gitconductor 0.1.0 \
51
51
 
52
52
  `--find-links` may be used more than once.
53
53
 
54
+ ## Use a uv Config File
55
+
56
+ Pass a specific uv configuration file to `uv tool install`:
57
+
58
+ ```sh
59
+ module-manager deploy-python internal-tool 1.2.3 \
60
+ --package internal-tool==1.2.3 \
61
+ --uv-config-file /prod/config/uv.toml \
62
+ --prefix /prod/tools \
63
+ --module-root /prod/modulefiles \
64
+ --execute-install
65
+ ```
66
+
67
+ The same option can be set in the module-manager config file:
68
+
69
+ ```toml
70
+ [python]
71
+ uv_config_file = "/prod/config/uv.toml"
72
+ ```
73
+
74
+ Use uv's own `UV_CONFIG_FILE` environment variable if you want to configure uv
75
+ through the environment.
76
+
54
77
  ## Deploy from VCS
55
78
 
56
79
  `--package` is passed directly to `uv tool install`, so VCS package specs are
@@ -44,6 +44,7 @@ module-manager deploy-python NAME VERSION \
44
44
  [--python PYTHON] \
45
45
  [--index URL] \
46
46
  [--find-links PATH_OR_URL] \
47
+ [--uv-config-file PATH] \
47
48
  [--prefix PATH] \
48
49
  [--module-root PATH] \
49
50
  [--no-default] \
@@ -1,3 +1,3 @@
1
1
  """Deployment helpers for HPC environment module files."""
2
2
 
3
- __version__ = "0.1.0"
3
+ __version__ = "0.4.0"
@@ -29,14 +29,61 @@ click.rich_click.STYLE_ARGUMENT = "bold yellow"
29
29
  click.rich_click.STYLE_SWITCH = "bold magenta"
30
30
  click.rich_click.STYLE_METAVAR = "yellow"
31
31
  click.rich_click.HEADER_TEXT = "module-manager"
32
- click.rich_click.FOOTER_TEXT = (
33
- "Examples: [cyan]module-manager deploy-python ruff 0.8.0 --package "
34
- "ruff==0.8.0 --prefix /prod/tools --module-root /prod/modulefiles[/cyan]"
35
- )
36
32
 
37
33
  PATH = click.Path(path_type=Path)
38
34
  ClickCommand = TypeVar("ClickCommand", bound=Command)
39
35
 
36
+ TOP_LEVEL_EXAMPLES = (
37
+ "Examples:\n\n"
38
+ "[cyan]module-manager deploy-python ruff 0.8.0 --package ruff==0.8.0 "
39
+ "--prefix /prod/tools --module-root /prod/modulefiles[/cyan]\n\n"
40
+ "[cyan]module-manager deploy-rust ripgrep 14.1.1 --binary ./target/release/rg "
41
+ "--prefix /prod/tools --module-root /prod/modulefiles[/cyan]\n\n"
42
+ "[cyan]module-manager deploy-script my-tool 1.0.0 --script ./scripts/my-tool "
43
+ "--prefix /prod/tools --module-root /prod/modulefiles[/cyan]\n\n"
44
+ "[cyan]module-manager deploy-env --file dev-tools.toml[/cyan]\n\n"
45
+ "[cyan]module-manager uninstall ruff 0.8.0 --prefix /prod/tools --module-root /prod/modulefiles[/cyan]"
46
+ )
47
+
48
+ DEPLOY_PYTHON_EXAMPLES = (
49
+ "Examples:\n\n"
50
+ "[cyan]module-manager deploy-python ruff 0.8.0 --package ruff==0.8.0 "
51
+ "--prefix /prod/tools --module-root /prod/modulefiles --execute-install[/cyan]\n\n"
52
+ "[cyan]module-manager deploy-python internal-tool 1.2.3 --package internal-tool==1.2.3 "
53
+ "--uv-config-file /prod/config/uv.toml --prefix /prod/tools --module-root /prod/modulefiles[/cyan]"
54
+ )
55
+
56
+ DEPLOY_RUST_EXAMPLES = (
57
+ "Examples:\n\n"
58
+ "[cyan]module-manager deploy-rust ripgrep 14.1.1 --binary ./target/release/rg "
59
+ "--prefix /prod/tools --module-root /prod/modulefiles[/cyan]\n\n"
60
+ "[cyan]module-manager deploy-rust ripgrep 14.1.1 --binary ./target/release/rg "
61
+ "--prefix /prod/tools --module-root /prod/modulefiles --dry-run[/cyan]"
62
+ )
63
+
64
+ DEPLOY_SCRIPT_EXAMPLES = (
65
+ "Examples:\n\n"
66
+ "[cyan]module-manager deploy-script my-tool 1.0.0 --script ./scripts/my-tool "
67
+ "--prefix /prod/tools --module-root /prod/modulefiles[/cyan]\n\n"
68
+ "[cyan]module-manager deploy-script my-tool 1.0.0 --script ./scripts/my-tool "
69
+ "--prefix /prod/tools --module-root /prod/modulefiles --dry-run[/cyan]"
70
+ )
71
+
72
+ DEPLOY_ENV_EXAMPLES = (
73
+ "Examples:\n\n"
74
+ "[cyan]module-manager deploy-env --file dev-tools.toml[/cyan]\n\n"
75
+ "[cyan]module-manager deploy-env --file dev-tools.toml --prefix /scratch/tools "
76
+ "--module-root /scratch/modulefiles --dry-run[/cyan]"
77
+ )
78
+
79
+ UNINSTALL_EXAMPLES = (
80
+ "Examples:\n\n"
81
+ "[cyan]module-manager uninstall ruff 0.8.0 --prefix /prod/tools "
82
+ "--module-root /prod/modulefiles[/cyan]\n\n"
83
+ "[cyan]module-manager uninstall ruff 0.8.0 --prefix /prod/tools "
84
+ "--module-root /prod/modulefiles --dry-run[/cyan]"
85
+ )
86
+
40
87
 
41
88
  def common_options(command: ClickCommand) -> ClickCommand:
42
89
  """Attach options shared by deployment commands.
@@ -108,6 +155,7 @@ def location_options(command: ClickCommand) -> ClickCommand:
108
155
  "Build versioned modulefiles for Python tools, Rust binaries, shell scripts, "
109
156
  "and collective environments."
110
157
  ),
158
+ epilog=TOP_LEVEL_EXAMPLES,
111
159
  )
112
160
  @click.version_option(__version__)
113
161
  @click.option(
@@ -131,7 +179,11 @@ def main(ctx: click.Context, config: Path | None) -> None:
131
179
  ctx.obj = load_config(config)
132
180
 
133
181
 
134
- @main.command("deploy-python", help="Write a modulefile for a uv tool install Python CLI.")
182
+ @main.command(
183
+ "deploy-python",
184
+ help="Write a modulefile for a uv tool install Python CLI.",
185
+ epilog=DEPLOY_PYTHON_EXAMPLES,
186
+ )
135
187
  @common_options
136
188
  @click.option("--package", "package", required=True, help="Package spec passed to uv tool install.")
137
189
  @click.option("--python", "python", help="Python interpreter/version passed to uv.")
@@ -147,6 +199,11 @@ def main(ctx: click.Context, config: Path | None) -> None:
147
199
  multiple=True,
148
200
  help="Directory or HTML page of packages passed to uv. May be used more than once.",
149
201
  )
202
+ @click.option(
203
+ "--uv-config-file",
204
+ type=PATH,
205
+ help="uv.toml file passed to uv tool install with --config-file.",
206
+ )
150
207
  @click.option(
151
208
  "--execute-install",
152
209
  is_flag=True,
@@ -166,6 +223,7 @@ def deploy_python(
166
223
  python: str | None,
167
224
  indexes: tuple[str, ...],
168
225
  find_links: tuple[str, ...],
226
+ uv_config_file: Path | None,
169
227
  execute_install: bool,
170
228
  ) -> None:
171
229
  """Write a modulefile for a [cyan]uv tool install[/cyan] Python CLI.
@@ -183,6 +241,7 @@ def deploy_python(
183
241
  python: Optional Python interpreter or version passed to uv.
184
242
  indexes: Additional package index URLs passed to uv.
185
243
  find_links: Wheelhouse directories or HTML package pages passed to uv.
244
+ uv_config_file: Optional uv configuration file passed to `uv tool`.
186
245
  execute_install: Whether to run `uv tool install` immediately.
187
246
 
188
247
  Raises:
@@ -204,6 +263,7 @@ def deploy_python(
204
263
  python=python,
205
264
  indexes=indexes or config.indexes,
206
265
  find_links=find_links or config.find_links,
266
+ uv_config_file=(uv_config_file.expanduser() if uv_config_file else config.uv_config_file),
207
267
  execute_install=execute_install,
208
268
  make_default=make_default,
209
269
  )
@@ -212,7 +272,11 @@ def deploy_python(
212
272
  )
213
273
 
214
274
 
215
- @main.command("deploy-rust", help="Copy a Rust CLI binary and write a modulefile for it.")
275
+ @main.command(
276
+ "deploy-rust",
277
+ help="Copy a Rust CLI binary and write a modulefile for it.",
278
+ epilog=DEPLOY_RUST_EXAMPLES,
279
+ )
216
280
  @common_options
217
281
  @click.option(
218
282
  "--binary",
@@ -277,7 +341,11 @@ def deploy_rust(
277
341
  )
278
342
 
279
343
 
280
- @main.command("deploy-script", help="Copy a shell script and write a modulefile for it.")
344
+ @main.command(
345
+ "deploy-script",
346
+ help="Copy a shell script and write a modulefile for it.",
347
+ epilog=DEPLOY_SCRIPT_EXAMPLES,
348
+ )
281
349
  @common_options
282
350
  @click.option(
283
351
  "--script",
@@ -341,7 +409,11 @@ def deploy_script(
341
409
  )
342
410
 
343
411
 
344
- @main.command("deploy-env", help="Deploy a collective environment from a TOML manifest.")
412
+ @main.command(
413
+ "deploy-env",
414
+ help="Deploy a collective environment from a TOML manifest.",
415
+ epilog=DEPLOY_ENV_EXAMPLES,
416
+ )
345
417
  @click.option(
346
418
  "--file",
347
419
  "manifest",
@@ -421,7 +493,11 @@ def deploy_env(
421
493
  print_environment_result(result, dry_run)
422
494
 
423
495
 
424
- @main.command("uninstall", help="Remove a deployed tool version and its modulefile.")
496
+ @main.command(
497
+ "uninstall",
498
+ help="Remove a deployed tool version and its modulefile.",
499
+ epilog=UNINSTALL_EXAMPLES,
500
+ )
425
501
  @location_options
426
502
  @click.option(
427
503
  "--keep-default",
@@ -22,12 +22,14 @@ class AppConfig:
22
22
  indexes: Default Python package indexes passed to `uv tool install`.
23
23
  find_links: Default Python package locations passed to
24
24
  `uv tool install`.
25
+ uv_config_file: Default uv configuration file passed to `uv tool`.
25
26
  """
26
27
 
27
28
  prefix: Path | None = None
28
29
  module_root: Path | None = None
29
30
  indexes: tuple[str, ...] = ()
30
31
  find_links: tuple[str, ...] = ()
32
+ uv_config_file: Path | None = None
31
33
 
32
34
 
33
35
  def default_config_path() -> Path:
@@ -81,6 +83,7 @@ def load_file_config(path: Path) -> AppConfig:
81
83
  module_root=optional_path(data.get("module_root"), "module_root"),
82
84
  indexes=string_tuple(python_data.get("indexes"), "python.indexes"),
83
85
  find_links=string_tuple(python_data.get("find_links"), "python.find_links"),
86
+ uv_config_file=optional_path(python_data.get("uv_config_file"), "python.uv_config_file"),
84
87
  )
85
88
 
86
89
 
@@ -98,6 +101,7 @@ def overlay_env_config(config: AppConfig) -> AppConfig:
98
101
  module_root=env_path("MODULE_MANAGER_MODULE_ROOT") or config.module_root,
99
102
  indexes=env_tuple("MODULE_MANAGER_INDEXES") or env_tuple("MODULE_MANAGER_INDEX") or config.indexes,
100
103
  find_links=env_tuple("MODULE_MANAGER_FIND_LINKS") or config.find_links,
104
+ uv_config_file=config.uv_config_file,
101
105
  )
102
106
 
103
107
 
@@ -62,6 +62,7 @@ class EnvironmentToolSpec:
62
62
  name: Tool name used for copied binaries and reporting.
63
63
  version: Optional source tool version for documentation.
64
64
  package: Python package spec passed to `uv tool install`.
65
+ uv_config_file: uv configuration file passed to `uv tool`.
65
66
  binary: Rust binary path to copy into the shared `bin` directory.
66
67
  script: Shell script path to copy into the shared `bin` directory.
67
68
  python: Optional Python interpreter or version passed to uv.
@@ -75,6 +76,7 @@ class EnvironmentToolSpec:
75
76
  name: str
76
77
  version: str | None = None
77
78
  package: str | None = None
79
+ uv_config_file: Path | None = None
78
80
  binary: Path | None = None
79
81
  script: Path | None = None
80
82
  python: str | None = None
@@ -148,6 +150,7 @@ def uv_install_command(
148
150
  python: str | None = None,
149
151
  indexes: tuple[str, ...] = (),
150
152
  find_links: tuple[str, ...] = (),
153
+ uv_config_file: Path | None = None,
151
154
  ) -> list[str]:
152
155
  """Build the uv command used to install a Python CLI tool.
153
156
 
@@ -156,11 +159,15 @@ def uv_install_command(
156
159
  python: Optional Python interpreter or version passed to uv.
157
160
  indexes: Additional package index URLs.
158
161
  find_links: Wheelhouse directories or HTML package pages.
162
+ uv_config_file: Optional uv configuration file passed to `uv tool`.
159
163
 
160
164
  Returns:
161
165
  Tokenized uv command suitable for `subprocess.run`.
162
166
  """
163
- command = ["uv", "tool", "install"]
167
+ command = ["uv", "tool"]
168
+ if uv_config_file:
169
+ command.extend(["--config-file", str(uv_config_file)])
170
+ command.append("install")
164
171
  if python:
165
172
  command.extend(["--python", python])
166
173
  for index in indexes:
@@ -482,6 +489,7 @@ def parse_environment_tool(data: object, base_dir: Path, index: int) -> Environm
482
489
  name=name,
483
490
  version=optional_string(data, "version"),
484
491
  package=optional_string(data, "package"),
492
+ uv_config_file=optional_manifest_path(data, "uv_config_file", base_dir),
485
493
  binary=optional_manifest_path(data, "binary", base_dir),
486
494
  script=optional_manifest_path(data, "script", base_dir),
487
495
  python=optional_string(data, "python"),
@@ -540,7 +548,13 @@ def environment_actions(spec: EnvironmentSpec, paths: DeploymentPaths) -> tuple[
540
548
  tool_dir = paths.install_root / "uv-tools"
541
549
  for tool in spec.tools:
542
550
  if tool.tool_type == "python" and tool.package:
543
- command = uv_install_command(tool.package, tool.python, tool.indexes, tool.find_links)
551
+ command = uv_install_command(
552
+ tool.package,
553
+ tool.python,
554
+ tool.indexes,
555
+ tool.find_links,
556
+ tool.uv_config_file,
557
+ )
544
558
  actions.append(
545
559
  f"install python {tool.name}: "
546
560
  f"UV_TOOL_DIR={shlex.quote(str(tool_dir))} "
@@ -595,7 +609,13 @@ def deploy_environment(
595
609
  env["UV_TOOL_DIR"] = str(tool_dir)
596
610
  env["UV_TOOL_BIN_DIR"] = str(paths.bin_dir)
597
611
  subprocess.run(
598
- uv_install_command(tool.package, tool.python, tool.indexes, tool.find_links),
612
+ uv_install_command(
613
+ tool.package,
614
+ tool.python,
615
+ tool.indexes,
616
+ tool.find_links,
617
+ tool.uv_config_file,
618
+ ),
599
619
  check=True,
600
620
  env=env,
601
621
  )
@@ -634,6 +654,7 @@ def deploy_python_tool(
634
654
  python: str | None = None,
635
655
  indexes: tuple[str, ...] = (),
636
656
  find_links: tuple[str, ...] = (),
657
+ uv_config_file: Path | None = None,
637
658
  execute_install: bool = False,
638
659
  make_default: bool = True,
639
660
  ) -> DeploymentPaths:
@@ -650,6 +671,7 @@ def deploy_python_tool(
650
671
  python: Optional Python interpreter or version passed to uv.
651
672
  indexes: Additional package index URLs passed to uv.
652
673
  find_links: Wheelhouse directories or HTML package pages passed to uv.
674
+ uv_config_file: Optional uv configuration file passed to `uv tool`.
653
675
  execute_install: Whether to run `uv tool install` immediately.
654
676
  make_default: Whether to make this version the module default.
655
677
 
@@ -664,7 +686,7 @@ def deploy_python_tool(
664
686
  paths = deployment_paths(module_root, prefix, name, version)
665
687
  paths.bin_dir.mkdir(parents=True, exist_ok=True)
666
688
  tool_dir = paths.install_root / "uv-tools"
667
- command = uv_install_command(package, python, indexes, find_links)
689
+ command = uv_install_command(package, python, indexes, find_links, uv_config_file)
668
690
 
669
691
  if execute_install:
670
692
  require_executable("uv")
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "env-module-manager"
3
- version = "0.3.0"
3
+ version = "0.4.0"
4
4
  description = "Deploy Python, Rust, shell, and collective CLI environments behind GNU environment modulefiles"
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.11"
@@ -56,12 +56,16 @@ def test_deploy_python_command_writes_modulefile() -> None:
56
56
  "https://packages.example/simple",
57
57
  "--find-links",
58
58
  "/prod/wheels",
59
+ "--uv-config-file",
60
+ "/prod/uv.toml",
59
61
  ],
60
62
  )
63
+ modulefile = Path("modules/ruff/0.8.0").read_text(encoding="utf-8")
61
64
 
62
65
  assert result.exit_code == 0
63
66
  assert "modulefile: modules/ruff/0.8.0" in result.output
64
67
  assert "default version: modules/ruff/.version" in result.output
68
+ assert "--config-file /prod/uv.toml" in modulefile
65
69
 
66
70
 
67
71
  def test_deploy_python_command_uses_config_defaults() -> None:
@@ -78,6 +82,7 @@ module_root = "modules"
78
82
  [python]
79
83
  indexes = ["https://packages.example/simple"]
80
84
  find_links = ["/prod/wheels"]
85
+ uv_config_file = "uv.toml"
81
86
  """.strip(),
82
87
  encoding="utf-8",
83
88
  )
@@ -93,9 +98,11 @@ find_links = ["/prod/wheels"]
93
98
  "ruff==0.8.0",
94
99
  ],
95
100
  )
101
+ modulefile = Path("modules/ruff/0.8.0").read_text(encoding="utf-8")
96
102
 
97
103
  assert result.exit_code == 0
98
104
  assert "modulefile: modules/ruff/0.8.0" in result.output
105
+ assert "--config-file uv.toml" in modulefile
99
106
 
100
107
 
101
108
  def test_cli_options_override_config_defaults() -> None:
@@ -22,6 +22,7 @@ module_root = "/scratch/modules"
22
22
  [python]
23
23
  indexes = ["https://packages.example/simple"]
24
24
  find_links = ["/scratch/wheels"]
25
+ uv_config_file = "/scratch/uv.toml"
25
26
  """.strip(),
26
27
  encoding="utf-8",
27
28
  )
@@ -31,6 +32,7 @@ find_links = ["/scratch/wheels"]
31
32
  module_root=Path("/scratch/modules"),
32
33
  indexes=("https://packages.example/simple",),
33
34
  find_links=("/scratch/wheels",),
35
+ uv_config_file=Path("/scratch/uv.toml"),
34
36
  )
35
37
 
36
38
 
@@ -109,6 +109,18 @@ def test_python_tool_install_command_accepts_python() -> None:
109
109
  ]
110
110
 
111
111
 
112
+ def test_python_tool_install_command_accepts_uv_config_file() -> None:
113
+ """Uv install commands should include a requested uv config file."""
114
+ assert uv_install_command("ruff==0.8.0", uv_config_file=Path("/prod/uv.toml")) == [
115
+ "uv",
116
+ "tool",
117
+ "--config-file",
118
+ "/prod/uv.toml",
119
+ "install",
120
+ "ruff==0.8.0",
121
+ ]
122
+
123
+
112
124
  def test_deploy_python_tool_writes_package_sources_to_install_hint(tmp_path: Path) -> None:
113
125
  """Generated module help should show the package source options.
114
126
 
@@ -123,9 +135,11 @@ def test_deploy_python_tool_writes_package_sources_to_install_hint(tmp_path: Pat
123
135
  prefix=tmp_path / "tools",
124
136
  indexes=("https://packages.example/simple",),
125
137
  find_links=("/prod/wheels",),
138
+ uv_config_file=Path("/prod/uv.toml"),
126
139
  )
127
140
 
128
141
  modulefile = paths.modulefile.read_text(encoding="utf-8")
142
+ assert "--config-file /prod/uv.toml" in modulefile
129
143
  assert "--index https://packages.example/simple" in modulefile
130
144
  assert "--find-links /prod/wheels" in modulefile
131
145
 
@@ -366,6 +380,7 @@ package = "ruff==0.8.0"
366
380
  python = "3.12"
367
381
  indexes = ["https://packages.example/simple"]
368
382
  find_links = ["/prod/wheels"]
383
+ uv_config_file = "uv.toml"
369
384
 
370
385
  [[tools]]
371
386
  type = "script"
@@ -384,6 +399,7 @@ script = "scripts/helper"
384
399
  assert not spec.make_default
385
400
  assert spec.tools[0].package == "ruff==0.8.0"
386
401
  assert spec.tools[0].indexes == ("https://packages.example/simple",)
402
+ assert spec.tools[0].uv_config_file == tmp_path / "uv.toml"
387
403
  assert spec.tools[1].script == tmp_path / "scripts/helper"
388
404
 
389
405
 
@@ -491,6 +507,7 @@ type = "python"
491
507
  name = "ruff"
492
508
  package = "ruff==0.8.0"
493
509
  python = "3.12"
510
+ uv_config_file = "uv.toml"
494
511
  """.strip(),
495
512
  encoding="utf-8",
496
513
  )
@@ -502,7 +519,16 @@ python = "3.12"
502
519
  prefix=tmp_path / "tools",
503
520
  )
504
521
 
505
- assert calls[0][0] == ["uv", "tool", "install", "--python", "3.12", "ruff==0.8.0"]
522
+ assert calls[0][0] == [
523
+ "uv",
524
+ "tool",
525
+ "--config-file",
526
+ str(tmp_path / "uv.toml"),
527
+ "install",
528
+ "--python",
529
+ "3.12",
530
+ "ruff==0.8.0",
531
+ ]
506
532
  assert calls[0][1]["UV_TOOL_DIR"] == str(result.paths.install_root / "uv-tools")
507
533
  assert calls[0][1]["UV_TOOL_BIN_DIR"] == str(result.paths.bin_dir)
508
534
  assert result.paths.modulefile.exists()
@@ -344,7 +344,7 @@ wheels = [
344
344
 
345
345
  [[package]]
346
346
  name = "env-module-manager"
347
- version = "0.3.0"
347
+ version = "0.4.0"
348
348
  source = { editable = "." }
349
349
  dependencies = [
350
350
  { name = "click" },