env-module-manager 0.5.0__tar.gz → 0.6.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.
- {env_module_manager-0.5.0 → env_module_manager-0.6.0}/PKG-INFO +2 -1
- {env_module_manager-0.5.0 → env_module_manager-0.6.0}/README.md +1 -0
- {env_module_manager-0.5.0 → env_module_manager-0.6.0}/docs/collective-environments.md +2 -0
- {env_module_manager-0.5.0 → env_module_manager-0.6.0}/docs/python-tools.md +15 -0
- {env_module_manager-0.5.0 → env_module_manager-0.6.0}/docs/reference.md +1 -0
- {env_module_manager-0.5.0 → env_module_manager-0.6.0}/module_manager/__init__.py +1 -1
- {env_module_manager-0.5.0 → env_module_manager-0.6.0}/module_manager/cli.py +9 -0
- {env_module_manager-0.5.0 → env_module_manager-0.6.0}/module_manager/deploy.py +35 -1
- {env_module_manager-0.5.0 → env_module_manager-0.6.0}/pyproject.toml +1 -1
- {env_module_manager-0.5.0 → env_module_manager-0.6.0}/tests/test_cli.py +5 -0
- env_module_manager-0.6.0/tests/test_integration.py +138 -0
- {env_module_manager-0.5.0 → env_module_manager-0.6.0}/tests/test_modulefile.py +46 -0
- {env_module_manager-0.5.0 → env_module_manager-0.6.0}/uv.lock +1 -1
- env_module_manager-0.5.0/tests/test_integration.py +0 -49
- {env_module_manager-0.5.0 → env_module_manager-0.6.0}/.github/workflows/publish.yml +0 -0
- {env_module_manager-0.5.0 → env_module_manager-0.6.0}/.github/workflows/tag.yml +0 -0
- {env_module_manager-0.5.0 → env_module_manager-0.6.0}/.github/workflows/test.yml +0 -0
- {env_module_manager-0.5.0 → env_module_manager-0.6.0}/.gitignore +0 -0
- {env_module_manager-0.5.0 → env_module_manager-0.6.0}/.python-version +0 -0
- {env_module_manager-0.5.0 → env_module_manager-0.6.0}/.readthedocs.yaml +0 -0
- {env_module_manager-0.5.0 → env_module_manager-0.6.0}/AGENTS.md +0 -0
- {env_module_manager-0.5.0 → env_module_manager-0.6.0}/CONTRIBUTING.md +0 -0
- {env_module_manager-0.5.0 → env_module_manager-0.6.0}/LICENSE.md +0 -0
- {env_module_manager-0.5.0 → env_module_manager-0.6.0}/docs/configuration.md +0 -0
- {env_module_manager-0.5.0 → env_module_manager-0.6.0}/docs/development.md +0 -0
- {env_module_manager-0.5.0 → env_module_manager-0.6.0}/docs/environment-modules.md +0 -0
- {env_module_manager-0.5.0 → env_module_manager-0.6.0}/docs/index.md +0 -0
- {env_module_manager-0.5.0 → env_module_manager-0.6.0}/docs/installation.md +0 -0
- {env_module_manager-0.5.0 → env_module_manager-0.6.0}/docs/rust-tools.md +0 -0
- {env_module_manager-0.5.0 → env_module_manager-0.6.0}/docs/shell-tools.md +0 -0
- {env_module_manager-0.5.0 → env_module_manager-0.6.0}/docs/stylesheets/extra.css +0 -0
- {env_module_manager-0.5.0 → env_module_manager-0.6.0}/docs/uninstall.md +0 -0
- {env_module_manager-0.5.0 → env_module_manager-0.6.0}/example.bash +0 -0
- {env_module_manager-0.5.0 → env_module_manager-0.6.0}/mkdocs.yml +0 -0
- {env_module_manager-0.5.0 → env_module_manager-0.6.0}/module_manager/config.py +0 -0
- {env_module_manager-0.5.0 → env_module_manager-0.6.0}/module_manager/modulefile.py +0 -0
- {env_module_manager-0.5.0 → env_module_manager-0.6.0}/tests/test_config.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: env-module-manager
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.6.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/
|
|
@@ -73,6 +73,7 @@ Deploy a Python CLI with `uv tool install`:
|
|
|
73
73
|
```sh
|
|
74
74
|
module-manager deploy-python ruff 0.8.0 \
|
|
75
75
|
--package 'ruff==0.8.0' \
|
|
76
|
+
--constraints /prod/constraints/runtime.txt \
|
|
76
77
|
--prefix /prod/tools \
|
|
77
78
|
--module-root /prod/modulefiles \
|
|
78
79
|
--execute-install
|
|
@@ -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
|
+
constraints = ["/prod/constraints/runtime.txt"]
|
|
25
26
|
uv_config_file = "/prod/config/uv.toml"
|
|
26
27
|
|
|
27
28
|
[[tools]]
|
|
@@ -59,6 +60,7 @@ module load dev-tools
|
|
|
59
60
|
```
|
|
60
61
|
|
|
61
62
|
Relative `binary` and `script` paths are resolved relative to the manifest file.
|
|
63
|
+
Relative Python `constraints` paths are resolved the same way.
|
|
62
64
|
|
|
63
65
|
## Preview
|
|
64
66
|
|
|
@@ -55,6 +55,21 @@ module-manager deploy-python gitconductor 0.1.0 \
|
|
|
55
55
|
|
|
56
56
|
`--find-links` may be used more than once.
|
|
57
57
|
|
|
58
|
+
## Use Constraint Files
|
|
59
|
+
|
|
60
|
+
Pass requirement constraint files to `uv tool install`:
|
|
61
|
+
|
|
62
|
+
```sh
|
|
63
|
+
module-manager deploy-python gitconductor 0.1.0 \
|
|
64
|
+
--package gitconductor==0.1.0 \
|
|
65
|
+
--constraints /prod/constraints/runtime.txt \
|
|
66
|
+
--prefix /prod/tools \
|
|
67
|
+
--module-root /prod/modulefiles \
|
|
68
|
+
--execute-install
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
`--constraints` may be used more than once.
|
|
72
|
+
|
|
58
73
|
## Use a uv Config File
|
|
59
74
|
|
|
60
75
|
Pass a specific uv configuration file to `uv tool install`:
|
|
@@ -199,6 +199,12 @@ def main(ctx: click.Context, config: Path | None) -> None:
|
|
|
199
199
|
multiple=True,
|
|
200
200
|
help="Directory or HTML page of packages passed to uv. May be used more than once.",
|
|
201
201
|
)
|
|
202
|
+
@click.option(
|
|
203
|
+
"--constraints",
|
|
204
|
+
"constraints",
|
|
205
|
+
multiple=True,
|
|
206
|
+
help="Requirements constraint file passed to uv. May be used more than once.",
|
|
207
|
+
)
|
|
202
208
|
@click.option(
|
|
203
209
|
"--uv-config-file",
|
|
204
210
|
type=PATH,
|
|
@@ -223,6 +229,7 @@ def deploy_python(
|
|
|
223
229
|
python: str | None,
|
|
224
230
|
indexes: tuple[str, ...],
|
|
225
231
|
find_links: tuple[str, ...],
|
|
232
|
+
constraints: tuple[str, ...],
|
|
226
233
|
uv_config_file: Path | None,
|
|
227
234
|
execute_install: bool,
|
|
228
235
|
) -> None:
|
|
@@ -241,6 +248,7 @@ def deploy_python(
|
|
|
241
248
|
python: Optional Python interpreter or version passed to uv.
|
|
242
249
|
indexes: Additional package index URLs passed to uv.
|
|
243
250
|
find_links: Wheelhouse directories or HTML package pages passed to uv.
|
|
251
|
+
constraints: Requirements constraint files passed to uv.
|
|
244
252
|
uv_config_file: Optional uv configuration file passed to `uv tool`.
|
|
245
253
|
execute_install: Whether to run `uv tool install` immediately.
|
|
246
254
|
|
|
@@ -265,6 +273,7 @@ def deploy_python(
|
|
|
265
273
|
python=python,
|
|
266
274
|
indexes=indexes or config.indexes,
|
|
267
275
|
find_links=find_links or config.find_links,
|
|
276
|
+
constraints=constraints,
|
|
268
277
|
uv_config_file=(uv_config_file.expanduser() if uv_config_file else config.uv_config_file),
|
|
269
278
|
execute_install=execute_install,
|
|
270
279
|
make_default=make_default,
|
|
@@ -68,6 +68,7 @@ class EnvironmentToolSpec:
|
|
|
68
68
|
python: Optional Python interpreter or version passed to uv.
|
|
69
69
|
indexes: Additional package index URLs passed to uv.
|
|
70
70
|
find_links: Wheelhouse directories or HTML package pages passed to uv.
|
|
71
|
+
constraints: Constraint requirement files passed to uv.
|
|
71
72
|
description: Optional tool description.
|
|
72
73
|
homepage: Optional upstream homepage.
|
|
73
74
|
"""
|
|
@@ -82,6 +83,7 @@ class EnvironmentToolSpec:
|
|
|
82
83
|
python: str | None = None
|
|
83
84
|
indexes: tuple[str, ...] = ()
|
|
84
85
|
find_links: tuple[str, ...] = ()
|
|
86
|
+
constraints: tuple[str, ...] = ()
|
|
85
87
|
description: str | None = None
|
|
86
88
|
homepage: str | None = None
|
|
87
89
|
|
|
@@ -150,6 +152,7 @@ def uv_install_command(
|
|
|
150
152
|
python: str | None = None,
|
|
151
153
|
indexes: tuple[str, ...] = (),
|
|
152
154
|
find_links: tuple[str, ...] = (),
|
|
155
|
+
constraints: tuple[str, ...] = (),
|
|
153
156
|
uv_config_file: Path | None = None,
|
|
154
157
|
) -> list[str]:
|
|
155
158
|
"""Build the uv command used to install a Python CLI tool.
|
|
@@ -159,6 +162,7 @@ def uv_install_command(
|
|
|
159
162
|
python: Optional Python interpreter or version passed to uv.
|
|
160
163
|
indexes: Additional package index URLs.
|
|
161
164
|
find_links: Wheelhouse directories or HTML package pages.
|
|
165
|
+
constraints: Constraint requirement files.
|
|
162
166
|
uv_config_file: Optional uv configuration file passed to `uv tool`.
|
|
163
167
|
|
|
164
168
|
Returns:
|
|
@@ -174,6 +178,8 @@ def uv_install_command(
|
|
|
174
178
|
command.extend(["--index", index])
|
|
175
179
|
for link in find_links:
|
|
176
180
|
command.extend(["--find-links", link])
|
|
181
|
+
for constraint in constraints:
|
|
182
|
+
command.extend(["--constraints", constraint])
|
|
177
183
|
command.append(package)
|
|
178
184
|
return command
|
|
179
185
|
|
|
@@ -400,6 +406,29 @@ def optional_manifest_path(data: dict[str, Any], key: str, base_dir: Path | None
|
|
|
400
406
|
return path
|
|
401
407
|
|
|
402
408
|
|
|
409
|
+
def optional_manifest_paths(data: dict[str, Any], key: str, base_dir: Path | None = None) -> tuple[Path, ...]:
|
|
410
|
+
"""Read optional paths from manifest data.
|
|
411
|
+
|
|
412
|
+
Args:
|
|
413
|
+
data: Manifest table.
|
|
414
|
+
key: Optional path-list key.
|
|
415
|
+
base_dir: Directory used to resolve relative paths.
|
|
416
|
+
|
|
417
|
+
Returns:
|
|
418
|
+
Expanded path values, or an empty tuple.
|
|
419
|
+
|
|
420
|
+
Raises:
|
|
421
|
+
TypeError: If the value is not a list of strings.
|
|
422
|
+
"""
|
|
423
|
+
paths = []
|
|
424
|
+
for value in optional_string_tuple(data, key):
|
|
425
|
+
path = Path(value).expanduser()
|
|
426
|
+
if base_dir and not path.is_absolute():
|
|
427
|
+
path = base_dir / path
|
|
428
|
+
paths.append(path)
|
|
429
|
+
return tuple(paths)
|
|
430
|
+
|
|
431
|
+
|
|
403
432
|
def optional_string_tuple(data: dict[str, Any], key: str) -> tuple[str, ...]:
|
|
404
433
|
"""Read an optional list of strings from manifest data.
|
|
405
434
|
|
|
@@ -512,6 +541,7 @@ def parse_environment_tool(data: object, base_dir: Path, index: int) -> Environm
|
|
|
512
541
|
python=optional_string(data, "python"),
|
|
513
542
|
indexes=optional_string_tuple(data, "indexes"),
|
|
514
543
|
find_links=optional_string_tuple(data, "find_links"),
|
|
544
|
+
constraints=tuple(str(path) for path in optional_manifest_paths(data, "constraints", base_dir)),
|
|
515
545
|
description=optional_string(data, "description"),
|
|
516
546
|
homepage=optional_string(data, "homepage"),
|
|
517
547
|
)
|
|
@@ -570,6 +600,7 @@ def environment_actions(spec: EnvironmentSpec, paths: DeploymentPaths) -> tuple[
|
|
|
570
600
|
tool.python,
|
|
571
601
|
tool.indexes,
|
|
572
602
|
tool.find_links,
|
|
603
|
+
tool.constraints,
|
|
573
604
|
tool.uv_config_file,
|
|
574
605
|
)
|
|
575
606
|
actions.append(
|
|
@@ -628,6 +659,7 @@ def deploy_environment(
|
|
|
628
659
|
tool.python,
|
|
629
660
|
tool.indexes,
|
|
630
661
|
tool.find_links,
|
|
662
|
+
tool.constraints,
|
|
631
663
|
tool.uv_config_file,
|
|
632
664
|
),
|
|
633
665
|
check=True,
|
|
@@ -668,6 +700,7 @@ def deploy_python_tool(
|
|
|
668
700
|
python: str | None = None,
|
|
669
701
|
indexes: tuple[str, ...] = (),
|
|
670
702
|
find_links: tuple[str, ...] = (),
|
|
703
|
+
constraints: tuple[str, ...] = (),
|
|
671
704
|
uv_config_file: Path | None = None,
|
|
672
705
|
execute_install: bool = False,
|
|
673
706
|
make_default: bool = True,
|
|
@@ -685,6 +718,7 @@ def deploy_python_tool(
|
|
|
685
718
|
python: Optional Python interpreter or version passed to uv.
|
|
686
719
|
indexes: Additional package index URLs passed to uv.
|
|
687
720
|
find_links: Wheelhouse directories or HTML package pages passed to uv.
|
|
721
|
+
constraints: Constraint requirement files passed to uv.
|
|
688
722
|
uv_config_file: Optional uv configuration file passed to `uv tool`.
|
|
689
723
|
execute_install: Whether to run `uv tool install` immediately.
|
|
690
724
|
make_default: Whether to make this version the module default.
|
|
@@ -700,7 +734,7 @@ def deploy_python_tool(
|
|
|
700
734
|
paths = deployment_paths(module_root, prefix, name, version)
|
|
701
735
|
paths.bin_dir.mkdir(parents=True, exist_ok=True)
|
|
702
736
|
tool_dir = paths.install_root / "uv-tools"
|
|
703
|
-
command = uv_install_command(package, python, indexes, find_links, uv_config_file)
|
|
737
|
+
command = uv_install_command(package, python, indexes, find_links, constraints, uv_config_file)
|
|
704
738
|
|
|
705
739
|
if execute_install:
|
|
706
740
|
require_executable("uv")
|
|
@@ -56,6 +56,8 @@ def test_deploy_python_command_writes_modulefile() -> None:
|
|
|
56
56
|
"https://packages.example/simple",
|
|
57
57
|
"--find-links",
|
|
58
58
|
"/prod/wheels",
|
|
59
|
+
"--constraints",
|
|
60
|
+
"/prod/constraints.txt",
|
|
59
61
|
"--uv-config-file",
|
|
60
62
|
"/prod/uv.toml",
|
|
61
63
|
],
|
|
@@ -66,6 +68,7 @@ def test_deploy_python_command_writes_modulefile() -> None:
|
|
|
66
68
|
assert "modulefile: modules/ruff/0.8.0" in result.output
|
|
67
69
|
assert "default version: modules/ruff/.version" in result.output
|
|
68
70
|
assert "--config-file /prod/uv.toml" in modulefile
|
|
71
|
+
assert "--constraints /prod/constraints.txt" in modulefile
|
|
69
72
|
|
|
70
73
|
|
|
71
74
|
def test_deploy_python_command_uses_config_defaults() -> None:
|
|
@@ -408,6 +411,7 @@ name = "ruff"
|
|
|
408
411
|
version = "0.8.0"
|
|
409
412
|
package = "ruff==0.8.0"
|
|
410
413
|
indexes = ["https://packages.example/simple"]
|
|
414
|
+
constraints = ["constraints.txt"]
|
|
411
415
|
""".strip(),
|
|
412
416
|
encoding="utf-8",
|
|
413
417
|
)
|
|
@@ -418,6 +422,7 @@ indexes = ["https://packages.example/simple"]
|
|
|
418
422
|
assert "would create install root: tools/dev-tools/2026.05" in result.output
|
|
419
423
|
assert "would install python ruff:" in result.output
|
|
420
424
|
assert "--index https://packages.example/simple" in result.output
|
|
425
|
+
assert "--constraints constraints.txt" in result.output
|
|
421
426
|
assert not install_root_exists
|
|
422
427
|
|
|
423
428
|
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
"""Integration tests that execute external deployment tools."""
|
|
2
|
+
|
|
3
|
+
import subprocess
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
|
|
6
|
+
import pytest
|
|
7
|
+
from click.testing import CliRunner
|
|
8
|
+
|
|
9
|
+
from module_manager.cli import main
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
@pytest.mark.integration
|
|
13
|
+
def test_deploy_python_real_uv_install_uses_cli_roots(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None:
|
|
14
|
+
"""A real uv install should use the CLI prefix and module root.
|
|
15
|
+
|
|
16
|
+
Args:
|
|
17
|
+
tmp_path: Temporary deployment root.
|
|
18
|
+
monkeypatch: Pytest helper used to control inherited uv destinations.
|
|
19
|
+
"""
|
|
20
|
+
project_root = Path(__file__).parent.parent
|
|
21
|
+
prefix = tmp_path / "tools"
|
|
22
|
+
module_root = tmp_path / "modules"
|
|
23
|
+
inherited_tool_dir = tmp_path / "inherited-tools"
|
|
24
|
+
inherited_bin_dir = tmp_path / "inherited-bin"
|
|
25
|
+
monkeypatch.setenv("UV_TOOL_DIR", str(inherited_tool_dir))
|
|
26
|
+
monkeypatch.setenv("UV_TOOL_BIN_DIR", str(inherited_bin_dir))
|
|
27
|
+
|
|
28
|
+
result = CliRunner().invoke(
|
|
29
|
+
main,
|
|
30
|
+
[
|
|
31
|
+
"deploy-python",
|
|
32
|
+
"module-manager",
|
|
33
|
+
"0.6.0",
|
|
34
|
+
"--package",
|
|
35
|
+
str(project_root),
|
|
36
|
+
"--prefix",
|
|
37
|
+
str(prefix),
|
|
38
|
+
"--module-root",
|
|
39
|
+
str(module_root),
|
|
40
|
+
"--execute-install",
|
|
41
|
+
],
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
install_root = prefix / "module-manager" / "0.6.0"
|
|
45
|
+
assert result.exit_code == 0, result.output
|
|
46
|
+
assert (install_root / "bin" / "module-manager").exists()
|
|
47
|
+
assert (install_root / "uv-tools" / "env-module-manager" / "uv-receipt.toml").exists()
|
|
48
|
+
assert (module_root / "module-manager" / "0.6.0").is_file()
|
|
49
|
+
assert not inherited_tool_dir.exists()
|
|
50
|
+
assert not inherited_bin_dir.exists()
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
@pytest.mark.integration
|
|
54
|
+
def test_deploy_python_real_uv_install_handles_url_dependency(tmp_path: Path) -> None:
|
|
55
|
+
"""A real uv install should resolve and run direct URL dependencies.
|
|
56
|
+
|
|
57
|
+
Args:
|
|
58
|
+
tmp_path: Temporary deployment root.
|
|
59
|
+
"""
|
|
60
|
+
dependency = tmp_path / "url-dependency"
|
|
61
|
+
dependency.mkdir()
|
|
62
|
+
(dependency / "pyproject.toml").write_text(
|
|
63
|
+
"""
|
|
64
|
+
[project]
|
|
65
|
+
name = "url-dependency"
|
|
66
|
+
version = "1.0.0"
|
|
67
|
+
requires-python = ">=3.11"
|
|
68
|
+
""".strip(),
|
|
69
|
+
encoding="utf-8",
|
|
70
|
+
)
|
|
71
|
+
dependency_package = dependency / "url_dependency"
|
|
72
|
+
dependency_package.mkdir()
|
|
73
|
+
(dependency_package / "__init__.py").write_text(
|
|
74
|
+
"""
|
|
75
|
+
def message() -> str:
|
|
76
|
+
return "url dependency works"
|
|
77
|
+
""".strip(),
|
|
78
|
+
encoding="utf-8",
|
|
79
|
+
)
|
|
80
|
+
|
|
81
|
+
tool = tmp_path / "url-tool"
|
|
82
|
+
tool.mkdir()
|
|
83
|
+
(tool / "pyproject.toml").write_text(
|
|
84
|
+
f"""
|
|
85
|
+
[project]
|
|
86
|
+
name = "url-tool"
|
|
87
|
+
version = "1.0.0"
|
|
88
|
+
requires-python = ">=3.11"
|
|
89
|
+
dependencies = [
|
|
90
|
+
"url-dependency @ {dependency.as_uri()}",
|
|
91
|
+
]
|
|
92
|
+
|
|
93
|
+
[project.scripts]
|
|
94
|
+
url-tool = "url_tool:main"
|
|
95
|
+
""".strip(),
|
|
96
|
+
encoding="utf-8",
|
|
97
|
+
)
|
|
98
|
+
tool_package = tool / "url_tool"
|
|
99
|
+
tool_package.mkdir()
|
|
100
|
+
(tool_package / "__init__.py").write_text(
|
|
101
|
+
"""
|
|
102
|
+
from url_dependency import message
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
def main() -> None:
|
|
106
|
+
print(message())
|
|
107
|
+
""".strip(),
|
|
108
|
+
encoding="utf-8",
|
|
109
|
+
)
|
|
110
|
+
|
|
111
|
+
prefix = tmp_path / "tools"
|
|
112
|
+
module_root = tmp_path / "modules"
|
|
113
|
+
result = CliRunner().invoke(
|
|
114
|
+
main,
|
|
115
|
+
[
|
|
116
|
+
"deploy-python",
|
|
117
|
+
"url-tool",
|
|
118
|
+
"1.0.0",
|
|
119
|
+
"--package",
|
|
120
|
+
str(tool),
|
|
121
|
+
"--prefix",
|
|
122
|
+
str(prefix),
|
|
123
|
+
"--module-root",
|
|
124
|
+
str(module_root),
|
|
125
|
+
"--execute-install",
|
|
126
|
+
],
|
|
127
|
+
)
|
|
128
|
+
|
|
129
|
+
executable = prefix / "url-tool" / "1.0.0" / "bin" / "url-tool"
|
|
130
|
+
assert result.exit_code == 0, result.output
|
|
131
|
+
assert executable.exists()
|
|
132
|
+
completed = subprocess.run(
|
|
133
|
+
[str(executable)],
|
|
134
|
+
check=True,
|
|
135
|
+
capture_output=True,
|
|
136
|
+
text=True,
|
|
137
|
+
)
|
|
138
|
+
assert completed.stdout.strip() == "url dependency works"
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"""Modulefile rendering and deployment helper tests."""
|
|
2
2
|
|
|
3
3
|
from pathlib import Path
|
|
4
|
+
from shlex import quote as shlex_quote
|
|
4
5
|
|
|
5
6
|
import pytest
|
|
6
7
|
|
|
@@ -84,6 +85,7 @@ def test_python_tool_install_command_accepts_indexes() -> None:
|
|
|
84
85
|
"internal-tool==1.2.3",
|
|
85
86
|
indexes=("https://packages.example/simple", "https://mirror.example/simple"),
|
|
86
87
|
find_links=("/prod/wheels",),
|
|
88
|
+
constraints=("/prod/constraints.txt",),
|
|
87
89
|
) == [
|
|
88
90
|
"uv",
|
|
89
91
|
"tool",
|
|
@@ -94,6 +96,8 @@ def test_python_tool_install_command_accepts_indexes() -> None:
|
|
|
94
96
|
"https://mirror.example/simple",
|
|
95
97
|
"--find-links",
|
|
96
98
|
"/prod/wheels",
|
|
99
|
+
"--constraints",
|
|
100
|
+
"/prod/constraints.txt",
|
|
97
101
|
"internal-tool==1.2.3",
|
|
98
102
|
]
|
|
99
103
|
|
|
@@ -122,6 +126,20 @@ def test_python_tool_install_command_accepts_uv_config_file() -> None:
|
|
|
122
126
|
]
|
|
123
127
|
|
|
124
128
|
|
|
129
|
+
def test_python_tool_install_command_accepts_url_dependency() -> None:
|
|
130
|
+
"""Uv install commands should pass direct URL dependencies unchanged."""
|
|
131
|
+
package = "mytool @ https://packages.example/files/mytool-1.0.0-py3-none-any.whl"
|
|
132
|
+
|
|
133
|
+
assert uv_install_command(package, constraints=("/prod/constraints.txt",)) == [
|
|
134
|
+
"uv",
|
|
135
|
+
"tool",
|
|
136
|
+
"install",
|
|
137
|
+
"--constraints",
|
|
138
|
+
"/prod/constraints.txt",
|
|
139
|
+
package,
|
|
140
|
+
]
|
|
141
|
+
|
|
142
|
+
|
|
125
143
|
def test_uv_install_environment_ignores_inherited_uv_tool_settings(
|
|
126
144
|
tmp_path: Path, monkeypatch: pytest.MonkeyPatch
|
|
127
145
|
) -> None:
|
|
@@ -158,6 +176,7 @@ def test_deploy_python_tool_writes_package_sources_to_install_hint(tmp_path: Pat
|
|
|
158
176
|
prefix=tmp_path / "tools",
|
|
159
177
|
indexes=("https://packages.example/simple",),
|
|
160
178
|
find_links=("/prod/wheels",),
|
|
179
|
+
constraints=("/prod/constraints.txt",),
|
|
161
180
|
uv_config_file=Path("/prod/uv.toml"),
|
|
162
181
|
)
|
|
163
182
|
|
|
@@ -165,6 +184,28 @@ def test_deploy_python_tool_writes_package_sources_to_install_hint(tmp_path: Pat
|
|
|
165
184
|
assert "--config-file /prod/uv.toml" in modulefile
|
|
166
185
|
assert "--index https://packages.example/simple" in modulefile
|
|
167
186
|
assert "--find-links /prod/wheels" in modulefile
|
|
187
|
+
assert "--constraints /prod/constraints.txt" in modulefile
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
def test_deploy_python_tool_writes_url_dependency_to_install_hint(tmp_path: Path) -> None:
|
|
191
|
+
"""Generated module help should preserve direct URL package specs.
|
|
192
|
+
|
|
193
|
+
Args:
|
|
194
|
+
tmp_path: Temporary deployment root.
|
|
195
|
+
"""
|
|
196
|
+
package = "mytool @ https://packages.example/files/mytool-1.0.0-py3-none-any.whl"
|
|
197
|
+
paths = deploy_python_tool(
|
|
198
|
+
name="mytool",
|
|
199
|
+
version="1.0.0",
|
|
200
|
+
package=package,
|
|
201
|
+
module_root=tmp_path / "modules",
|
|
202
|
+
prefix=tmp_path / "tools",
|
|
203
|
+
constraints=("/prod/constraints.txt",),
|
|
204
|
+
)
|
|
205
|
+
|
|
206
|
+
modulefile = paths.modulefile.read_text(encoding="utf-8")
|
|
207
|
+
assert shlex_quote(package) in modulefile
|
|
208
|
+
assert "--constraints /prod/constraints.txt" in modulefile
|
|
168
209
|
|
|
169
210
|
|
|
170
211
|
def test_require_executable_reports_missing_command(monkeypatch: pytest.MonkeyPatch) -> None:
|
|
@@ -403,6 +444,7 @@ package = "ruff==0.8.0"
|
|
|
403
444
|
python = "3.12"
|
|
404
445
|
indexes = ["https://packages.example/simple"]
|
|
405
446
|
find_links = ["/prod/wheels"]
|
|
447
|
+
constraints = ["constraints.txt", "/prod/global-constraints.txt"]
|
|
406
448
|
uv_config_file = "uv.toml"
|
|
407
449
|
|
|
408
450
|
[[tools]]
|
|
@@ -422,6 +464,7 @@ script = "scripts/helper"
|
|
|
422
464
|
assert not spec.make_default
|
|
423
465
|
assert spec.tools[0].package == "ruff==0.8.0"
|
|
424
466
|
assert spec.tools[0].indexes == ("https://packages.example/simple",)
|
|
467
|
+
assert spec.tools[0].constraints == (str(tmp_path / "constraints.txt"), "/prod/global-constraints.txt")
|
|
425
468
|
assert spec.tools[0].uv_config_file == tmp_path / "uv.toml"
|
|
426
469
|
assert spec.tools[1].script == tmp_path / "scripts/helper"
|
|
427
470
|
|
|
@@ -530,6 +573,7 @@ type = "python"
|
|
|
530
573
|
name = "ruff"
|
|
531
574
|
package = "ruff==0.8.0"
|
|
532
575
|
python = "3.12"
|
|
576
|
+
constraints = ["constraints.txt"]
|
|
533
577
|
uv_config_file = "uv.toml"
|
|
534
578
|
""".strip(),
|
|
535
579
|
encoding="utf-8",
|
|
@@ -550,6 +594,8 @@ uv_config_file = "uv.toml"
|
|
|
550
594
|
"install",
|
|
551
595
|
"--python",
|
|
552
596
|
"3.12",
|
|
597
|
+
"--constraints",
|
|
598
|
+
str(tmp_path / "constraints.txt"),
|
|
553
599
|
"ruff==0.8.0",
|
|
554
600
|
]
|
|
555
601
|
assert calls[0][1]["UV_TOOL_DIR"] == str(result.paths.install_root / "uv-tools")
|
|
@@ -1,49 +0,0 @@
|
|
|
1
|
-
"""Integration tests that execute external deployment tools."""
|
|
2
|
-
|
|
3
|
-
from pathlib import Path
|
|
4
|
-
|
|
5
|
-
import pytest
|
|
6
|
-
from click.testing import CliRunner
|
|
7
|
-
|
|
8
|
-
from module_manager.cli import main
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
@pytest.mark.integration
|
|
12
|
-
def test_deploy_python_real_uv_install_uses_cli_roots(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None:
|
|
13
|
-
"""A real uv install should use the CLI prefix and module root.
|
|
14
|
-
|
|
15
|
-
Args:
|
|
16
|
-
tmp_path: Temporary deployment root.
|
|
17
|
-
monkeypatch: Pytest helper used to control inherited uv destinations.
|
|
18
|
-
"""
|
|
19
|
-
project_root = Path(__file__).parent.parent
|
|
20
|
-
prefix = tmp_path / "tools"
|
|
21
|
-
module_root = tmp_path / "modules"
|
|
22
|
-
inherited_tool_dir = tmp_path / "inherited-tools"
|
|
23
|
-
inherited_bin_dir = tmp_path / "inherited-bin"
|
|
24
|
-
monkeypatch.setenv("UV_TOOL_DIR", str(inherited_tool_dir))
|
|
25
|
-
monkeypatch.setenv("UV_TOOL_BIN_DIR", str(inherited_bin_dir))
|
|
26
|
-
|
|
27
|
-
result = CliRunner().invoke(
|
|
28
|
-
main,
|
|
29
|
-
[
|
|
30
|
-
"deploy-python",
|
|
31
|
-
"module-manager",
|
|
32
|
-
"0.5.0",
|
|
33
|
-
"--package",
|
|
34
|
-
str(project_root),
|
|
35
|
-
"--prefix",
|
|
36
|
-
str(prefix),
|
|
37
|
-
"--module-root",
|
|
38
|
-
str(module_root),
|
|
39
|
-
"--execute-install",
|
|
40
|
-
],
|
|
41
|
-
)
|
|
42
|
-
|
|
43
|
-
install_root = prefix / "module-manager" / "0.5.0"
|
|
44
|
-
assert result.exit_code == 0, result.output
|
|
45
|
-
assert (install_root / "bin" / "module-manager").exists()
|
|
46
|
-
assert (install_root / "uv-tools" / "env-module-manager" / "uv-receipt.toml").exists()
|
|
47
|
-
assert (module_root / "module-manager" / "0.5.0").is_file()
|
|
48
|
-
assert not inherited_tool_dir.exists()
|
|
49
|
-
assert not inherited_bin_dir.exists()
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|