conda-workspaces 0.1.0__py3-none-any.whl
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.
- conda_workspaces/__init__.py +8 -0
- conda_workspaces/__main__.py +29 -0
- conda_workspaces/_version.py +34 -0
- conda_workspaces/cli/__init__.py +7 -0
- conda_workspaces/cli/activate.py +33 -0
- conda_workspaces/cli/add.py +91 -0
- conda_workspaces/cli/clean.py +52 -0
- conda_workspaces/cli/info.py +71 -0
- conda_workspaces/cli/init.py +114 -0
- conda_workspaces/cli/install.py +67 -0
- conda_workspaces/cli/list.py +55 -0
- conda_workspaces/cli/lock.py +43 -0
- conda_workspaces/cli/main.py +320 -0
- conda_workspaces/cli/remove.py +98 -0
- conda_workspaces/cli/run.py +64 -0
- conda_workspaces/cli/shell.py +46 -0
- conda_workspaces/context.py +87 -0
- conda_workspaces/env_export.py +94 -0
- conda_workspaces/env_spec.py +198 -0
- conda_workspaces/envs.py +209 -0
- conda_workspaces/exceptions.py +130 -0
- conda_workspaces/lockfile.py +222 -0
- conda_workspaces/models.py +235 -0
- conda_workspaces/parsers/__init__.py +99 -0
- conda_workspaces/parsers/base.py +42 -0
- conda_workspaces/parsers/pixi_toml.py +133 -0
- conda_workspaces/parsers/pyproject_toml.py +161 -0
- conda_workspaces/parsers/toml.py +145 -0
- conda_workspaces/plugin.py +68 -0
- conda_workspaces/resolver.py +119 -0
- conda_workspaces-0.1.0.dist-info/METADATA +144 -0
- conda_workspaces-0.1.0.dist-info/RECORD +35 -0
- conda_workspaces-0.1.0.dist-info/WHEEL +4 -0
- conda_workspaces-0.1.0.dist-info/entry_points.txt +5 -0
- conda_workspaces-0.1.0.dist-info/licenses/LICENSE +28 -0
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
"""Standalone CLI entry point for ``cw`` (short for ``conda workspace``).
|
|
2
|
+
|
|
3
|
+
This module allows running conda-workspaces without going through the
|
|
4
|
+
conda plugin dispatch::
|
|
5
|
+
|
|
6
|
+
cw init
|
|
7
|
+
cw install
|
|
8
|
+
cw list
|
|
9
|
+
cw add -e dev pytest
|
|
10
|
+
|
|
11
|
+
It reuses the same parser and execute logic as ``conda workspace``.
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
from __future__ import annotations
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def main(args: list[str] | None = None) -> None:
|
|
18
|
+
"""Entry point for the ``cw`` console script."""
|
|
19
|
+
from .cli.main import execute, generate_parser
|
|
20
|
+
|
|
21
|
+
parser = generate_parser()
|
|
22
|
+
parser.prog = "cw"
|
|
23
|
+
|
|
24
|
+
parsed = parser.parse_args(args)
|
|
25
|
+
raise SystemExit(execute(parsed))
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
if __name__ == "__main__":
|
|
29
|
+
main()
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# file generated by setuptools-scm
|
|
2
|
+
# don't change, don't track in version control
|
|
3
|
+
|
|
4
|
+
__all__ = [
|
|
5
|
+
"__version__",
|
|
6
|
+
"__version_tuple__",
|
|
7
|
+
"version",
|
|
8
|
+
"version_tuple",
|
|
9
|
+
"__commit_id__",
|
|
10
|
+
"commit_id",
|
|
11
|
+
]
|
|
12
|
+
|
|
13
|
+
TYPE_CHECKING = False
|
|
14
|
+
if TYPE_CHECKING:
|
|
15
|
+
from typing import Tuple
|
|
16
|
+
from typing import Union
|
|
17
|
+
|
|
18
|
+
VERSION_TUPLE = Tuple[Union[int, str], ...]
|
|
19
|
+
COMMIT_ID = Union[str, None]
|
|
20
|
+
else:
|
|
21
|
+
VERSION_TUPLE = object
|
|
22
|
+
COMMIT_ID = object
|
|
23
|
+
|
|
24
|
+
version: str
|
|
25
|
+
__version__: str
|
|
26
|
+
__version_tuple__: VERSION_TUPLE
|
|
27
|
+
version_tuple: VERSION_TUPLE
|
|
28
|
+
commit_id: COMMIT_ID
|
|
29
|
+
__commit_id__: COMMIT_ID
|
|
30
|
+
|
|
31
|
+
__version__ = version = '0.1.0'
|
|
32
|
+
__version_tuple__ = version_tuple = (0, 1, 0)
|
|
33
|
+
|
|
34
|
+
__commit_id__ = commit_id = None
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
"""``conda workspace activate`` — print activation instructions."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import TYPE_CHECKING
|
|
6
|
+
|
|
7
|
+
from conda.cli.common import print_activate
|
|
8
|
+
|
|
9
|
+
from ..context import WorkspaceContext
|
|
10
|
+
from ..exceptions import EnvironmentNotFoundError, EnvironmentNotInstalledError
|
|
11
|
+
from ..parsers import detect_and_parse
|
|
12
|
+
|
|
13
|
+
if TYPE_CHECKING:
|
|
14
|
+
import argparse
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def execute_activate(args: argparse.Namespace) -> int:
|
|
18
|
+
"""Print activation instructions for a workspace environment."""
|
|
19
|
+
manifest_path = getattr(args, "file", None)
|
|
20
|
+
_, config = detect_and_parse(manifest_path)
|
|
21
|
+
ctx = WorkspaceContext(config)
|
|
22
|
+
|
|
23
|
+
env_name = args.env_name
|
|
24
|
+
|
|
25
|
+
if env_name not in config.environments:
|
|
26
|
+
raise EnvironmentNotFoundError(env_name, list(config.environments.keys()))
|
|
27
|
+
|
|
28
|
+
if not ctx.env_exists(env_name):
|
|
29
|
+
raise EnvironmentNotInstalledError(env_name)
|
|
30
|
+
|
|
31
|
+
prefix = ctx.env_prefix(env_name)
|
|
32
|
+
print_activate(str(prefix))
|
|
33
|
+
return 0
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
"""``conda workspace add`` — add dependencies to the workspace manifest."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import TYPE_CHECKING
|
|
6
|
+
|
|
7
|
+
import tomlkit
|
|
8
|
+
|
|
9
|
+
from ..parsers import detect_workspace_file
|
|
10
|
+
|
|
11
|
+
if TYPE_CHECKING:
|
|
12
|
+
import argparse
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def execute_add(args: argparse.Namespace) -> int:
|
|
16
|
+
"""Add dependencies to the workspace manifest."""
|
|
17
|
+
manifest_path = getattr(args, "file", None) or detect_workspace_file()
|
|
18
|
+
specs = args.specs
|
|
19
|
+
is_pypi = getattr(args, "pypi", False)
|
|
20
|
+
feature = getattr(args, "feature", None)
|
|
21
|
+
environment = getattr(args, "environment", None)
|
|
22
|
+
|
|
23
|
+
# Determine which feature to target
|
|
24
|
+
target_feature = feature or environment # environment name == feature name
|
|
25
|
+
|
|
26
|
+
text = manifest_path.read_text(encoding="utf-8")
|
|
27
|
+
doc = tomlkit.loads(text)
|
|
28
|
+
|
|
29
|
+
dep_key = "pypi-dependencies" if is_pypi else "dependencies"
|
|
30
|
+
|
|
31
|
+
if manifest_path.name == "pyproject.toml":
|
|
32
|
+
_add_to_pyproject(doc, specs, dep_key, target_feature)
|
|
33
|
+
else:
|
|
34
|
+
_add_to_toml(doc, specs, dep_key, target_feature)
|
|
35
|
+
|
|
36
|
+
manifest_path.write_text(tomlkit.dumps(doc), encoding="utf-8")
|
|
37
|
+
|
|
38
|
+
label = "PyPI" if is_pypi else "conda"
|
|
39
|
+
location = f"feature '{target_feature}'" if target_feature else "default"
|
|
40
|
+
|
|
41
|
+
print(
|
|
42
|
+
f"Added {len(specs)} {label} dependency(ies)"
|
|
43
|
+
f" to {location} in {manifest_path.name}"
|
|
44
|
+
)
|
|
45
|
+
return 0
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def _add_to_toml(
|
|
49
|
+
doc: tomlkit.TOMLDocument,
|
|
50
|
+
specs: list[str],
|
|
51
|
+
dep_key: str,
|
|
52
|
+
feature: str | None,
|
|
53
|
+
) -> None:
|
|
54
|
+
"""Add deps to a pixi.toml or conda.toml document."""
|
|
55
|
+
if feature:
|
|
56
|
+
feat_table = doc.setdefault("feature", tomlkit.table())
|
|
57
|
+
target = feat_table.setdefault(feature, tomlkit.table())
|
|
58
|
+
else:
|
|
59
|
+
target = doc
|
|
60
|
+
|
|
61
|
+
deps = target.setdefault(dep_key, tomlkit.table())
|
|
62
|
+
for spec in specs:
|
|
63
|
+
name, _, version = spec.partition(" ")
|
|
64
|
+
deps[name] = version.strip() if version.strip() else "*"
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def _add_to_pyproject(
|
|
68
|
+
doc: tomlkit.TOMLDocument,
|
|
69
|
+
specs: list[str],
|
|
70
|
+
dep_key: str,
|
|
71
|
+
feature: str | None,
|
|
72
|
+
) -> None:
|
|
73
|
+
"""Add deps to a pyproject.toml with [tool.pixi.*] tables."""
|
|
74
|
+
tool = doc.setdefault("tool", tomlkit.table())
|
|
75
|
+
|
|
76
|
+
# Prefer conda-workspaces table if it exists, else pixi
|
|
77
|
+
if "conda-workspaces" in tool:
|
|
78
|
+
source = tool["conda-workspaces"]
|
|
79
|
+
else:
|
|
80
|
+
source = tool.setdefault("pixi", tomlkit.table())
|
|
81
|
+
|
|
82
|
+
if feature:
|
|
83
|
+
feat_table = source.setdefault("feature", tomlkit.table())
|
|
84
|
+
target = feat_table.setdefault(feature, tomlkit.table())
|
|
85
|
+
else:
|
|
86
|
+
target = source
|
|
87
|
+
|
|
88
|
+
deps = target.setdefault(dep_key, tomlkit.table())
|
|
89
|
+
for spec in specs:
|
|
90
|
+
name, _, version = spec.partition(" ")
|
|
91
|
+
deps[name] = version.strip() if version.strip() else "*"
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
"""``conda workspace clean`` — remove installed workspace environments."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import TYPE_CHECKING
|
|
6
|
+
|
|
7
|
+
from conda.exceptions import CondaSystemExit, DryRunExit
|
|
8
|
+
from conda.reporters import confirm_yn
|
|
9
|
+
|
|
10
|
+
from ..context import WorkspaceContext
|
|
11
|
+
from ..envs import clean_all, list_installed_environments, remove_environment
|
|
12
|
+
from ..parsers import detect_and_parse
|
|
13
|
+
|
|
14
|
+
if TYPE_CHECKING:
|
|
15
|
+
import argparse
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def execute_clean(args: argparse.Namespace) -> int:
|
|
19
|
+
"""Remove installed workspace environments."""
|
|
20
|
+
manifest_path = getattr(args, "file", None)
|
|
21
|
+
_, config = detect_and_parse(manifest_path)
|
|
22
|
+
ctx = WorkspaceContext(config)
|
|
23
|
+
|
|
24
|
+
env_name = getattr(args, "environment", None)
|
|
25
|
+
|
|
26
|
+
try:
|
|
27
|
+
if env_name:
|
|
28
|
+
if not ctx.env_exists(env_name):
|
|
29
|
+
print(f"Environment '{env_name}' is not installed.")
|
|
30
|
+
return 0
|
|
31
|
+
|
|
32
|
+
confirm_yn(f"Remove environment '{env_name}'?")
|
|
33
|
+
|
|
34
|
+
remove_environment(ctx, env_name)
|
|
35
|
+
print(f"Removed environment '{env_name}'.")
|
|
36
|
+
else:
|
|
37
|
+
installed = list_installed_environments(ctx)
|
|
38
|
+
if not installed:
|
|
39
|
+
print("No environments installed.")
|
|
40
|
+
return 0
|
|
41
|
+
|
|
42
|
+
print(f"This will remove {len(installed)} environment(s):")
|
|
43
|
+
for name in installed:
|
|
44
|
+
print(f" - {name}")
|
|
45
|
+
confirm_yn("Continue?")
|
|
46
|
+
|
|
47
|
+
clean_all(ctx)
|
|
48
|
+
print(f"Removed {len(installed)} environment(s).")
|
|
49
|
+
except (CondaSystemExit, DryRunExit):
|
|
50
|
+
return 0
|
|
51
|
+
|
|
52
|
+
return 0
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
"""``conda workspace info`` — show environment details."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import json
|
|
6
|
+
from typing import TYPE_CHECKING
|
|
7
|
+
|
|
8
|
+
from ..context import WorkspaceContext
|
|
9
|
+
from ..envs import get_environment_info
|
|
10
|
+
from ..parsers import detect_and_parse
|
|
11
|
+
from ..resolver import resolve_environment
|
|
12
|
+
|
|
13
|
+
if TYPE_CHECKING:
|
|
14
|
+
import argparse
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def execute_info(args: argparse.Namespace) -> int:
|
|
18
|
+
"""Show details about an environment."""
|
|
19
|
+
manifest_path = getattr(args, "file", None)
|
|
20
|
+
_, config = detect_and_parse(manifest_path)
|
|
21
|
+
ctx = WorkspaceContext(config)
|
|
22
|
+
|
|
23
|
+
env_name = args.env_name
|
|
24
|
+
json_output = getattr(args, "json", False)
|
|
25
|
+
|
|
26
|
+
resolved = resolve_environment(config, env_name, ctx.platform)
|
|
27
|
+
install_info = get_environment_info(ctx, env_name)
|
|
28
|
+
|
|
29
|
+
info = {
|
|
30
|
+
"name": env_name,
|
|
31
|
+
"prefix": str(ctx.env_prefix(env_name)),
|
|
32
|
+
"installed": install_info["exists"],
|
|
33
|
+
"channels": [ch.canonical_name for ch in resolved.channels],
|
|
34
|
+
"platforms": resolved.platforms,
|
|
35
|
+
"solve_group": resolved.solve_group,
|
|
36
|
+
"conda_dependencies": {
|
|
37
|
+
name: dep.conda_build_form()
|
|
38
|
+
for name, dep in resolved.conda_dependencies.items()
|
|
39
|
+
},
|
|
40
|
+
"pypi_dependencies": {
|
|
41
|
+
name: str(dep) for name, dep in resolved.pypi_dependencies.items()
|
|
42
|
+
},
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
if install_info["exists"]:
|
|
46
|
+
info["packages_installed"] = install_info.get("packages", 0)
|
|
47
|
+
|
|
48
|
+
if json_output:
|
|
49
|
+
print(json.dumps(info, indent=2))
|
|
50
|
+
else:
|
|
51
|
+
print(f"Environment: {info['name']}")
|
|
52
|
+
print(f"Prefix: {info['prefix']}")
|
|
53
|
+
print(f"Installed: {'yes' if info['installed'] else 'no'}")
|
|
54
|
+
if info["installed"]:
|
|
55
|
+
print(f"Packages: {info.get('packages_installed', '?')}")
|
|
56
|
+
print(f"Channels: {', '.join(info['channels']) or '(none)'}")
|
|
57
|
+
print(f"Platforms: {', '.join(info['platforms']) or '(all)'}")
|
|
58
|
+
if info["solve_group"]:
|
|
59
|
+
print(f"Solve group: {info['solve_group']}")
|
|
60
|
+
|
|
61
|
+
if info["conda_dependencies"]:
|
|
62
|
+
print("\nConda dependencies:")
|
|
63
|
+
for name, spec in sorted(info["conda_dependencies"].items()):
|
|
64
|
+
print(f" - {spec}")
|
|
65
|
+
|
|
66
|
+
if info["pypi_dependencies"]:
|
|
67
|
+
print("\nPyPI dependencies:")
|
|
68
|
+
for name, spec in sorted(info["pypi_dependencies"].items()):
|
|
69
|
+
print(f" - {spec}")
|
|
70
|
+
|
|
71
|
+
return 0
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
"""``conda workspace init`` — scaffold a new workspace manifest."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
from typing import TYPE_CHECKING
|
|
7
|
+
|
|
8
|
+
import tomlkit
|
|
9
|
+
from conda.base.context import context as conda_context
|
|
10
|
+
|
|
11
|
+
from ..exceptions import ManifestExistsError
|
|
12
|
+
|
|
13
|
+
if TYPE_CHECKING:
|
|
14
|
+
import argparse
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def execute_init(args: argparse.Namespace) -> int:
|
|
18
|
+
"""Create a new workspace manifest in the current directory."""
|
|
19
|
+
fmt = args.manifest_format
|
|
20
|
+
name = args.name or Path.cwd().name
|
|
21
|
+
channels = args.channels or ["conda-forge"]
|
|
22
|
+
|
|
23
|
+
if args.platforms:
|
|
24
|
+
platforms = args.platforms
|
|
25
|
+
else:
|
|
26
|
+
platforms = _detect_platforms()
|
|
27
|
+
|
|
28
|
+
if fmt == "pixi":
|
|
29
|
+
return _write_pixi_toml(name, channels, platforms)
|
|
30
|
+
elif fmt == "conda":
|
|
31
|
+
return _write_conda_toml(name, channels, platforms)
|
|
32
|
+
elif fmt == "pyproject":
|
|
33
|
+
return _write_pyproject_toml(name, channels, platforms)
|
|
34
|
+
|
|
35
|
+
return 1
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def _detect_platforms() -> list[str]:
|
|
39
|
+
"""Auto-detect a reasonable set of platforms."""
|
|
40
|
+
current = conda_context.subdir
|
|
41
|
+
# Include a sensible default set based on current platform
|
|
42
|
+
defaults = {"linux-64", "osx-64", "osx-arm64", "win-64"}
|
|
43
|
+
defaults.add(current)
|
|
44
|
+
return sorted(defaults)
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def _write_pixi_toml(name: str, channels: list[str], platforms: list[str]) -> int:
|
|
48
|
+
path = Path("pixi.toml")
|
|
49
|
+
if path.exists():
|
|
50
|
+
raise ManifestExistsError(path)
|
|
51
|
+
|
|
52
|
+
doc = tomlkit.document()
|
|
53
|
+
|
|
54
|
+
ws = tomlkit.table()
|
|
55
|
+
ws.add("name", name)
|
|
56
|
+
ws.add("version", "0.1.0")
|
|
57
|
+
ws.add("channels", channels)
|
|
58
|
+
ws.add("platforms", platforms)
|
|
59
|
+
doc.add("workspace", ws)
|
|
60
|
+
|
|
61
|
+
deps = tomlkit.table()
|
|
62
|
+
doc.add("dependencies", deps)
|
|
63
|
+
|
|
64
|
+
path.write_text(tomlkit.dumps(doc), encoding="utf-8")
|
|
65
|
+
print(f"Created {path}")
|
|
66
|
+
return 0
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def _write_conda_toml(name: str, channels: list[str], platforms: list[str]) -> int:
|
|
70
|
+
path = Path("conda.toml")
|
|
71
|
+
if path.exists():
|
|
72
|
+
raise ManifestExistsError(path)
|
|
73
|
+
|
|
74
|
+
doc = tomlkit.document()
|
|
75
|
+
|
|
76
|
+
ws = tomlkit.table()
|
|
77
|
+
ws.add("name", name)
|
|
78
|
+
ws.add("version", "0.1.0")
|
|
79
|
+
ws.add("channels", channels)
|
|
80
|
+
ws.add("platforms", platforms)
|
|
81
|
+
doc.add("workspace", ws)
|
|
82
|
+
|
|
83
|
+
deps = tomlkit.table()
|
|
84
|
+
doc.add("dependencies", deps)
|
|
85
|
+
|
|
86
|
+
path.write_text(tomlkit.dumps(doc), encoding="utf-8")
|
|
87
|
+
print(f"Created {path}")
|
|
88
|
+
return 0
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
def _write_pyproject_toml(name: str, channels: list[str], platforms: list[str]) -> int:
|
|
92
|
+
path = Path("pyproject.toml")
|
|
93
|
+
|
|
94
|
+
if path.exists():
|
|
95
|
+
text = path.read_text(encoding="utf-8")
|
|
96
|
+
doc = tomlkit.loads(text)
|
|
97
|
+
else:
|
|
98
|
+
doc = tomlkit.document()
|
|
99
|
+
|
|
100
|
+
tool = doc.setdefault("tool", tomlkit.table())
|
|
101
|
+
if "pixi" in tool:
|
|
102
|
+
raise ManifestExistsError("[tool.pixi] in pyproject.toml")
|
|
103
|
+
|
|
104
|
+
pixi = tomlkit.table()
|
|
105
|
+
ws = tomlkit.table()
|
|
106
|
+
ws.add("channels", channels)
|
|
107
|
+
ws.add("platforms", platforms)
|
|
108
|
+
pixi.add("workspace", ws)
|
|
109
|
+
pixi.add("dependencies", tomlkit.table())
|
|
110
|
+
tool.add("pixi", pixi)
|
|
111
|
+
|
|
112
|
+
path.write_text(tomlkit.dumps(doc), encoding="utf-8")
|
|
113
|
+
print(f"{'Updated' if path.exists() else 'Created'} {path}")
|
|
114
|
+
return 0
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
"""``conda workspace install`` — create or update workspace environments."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import TYPE_CHECKING
|
|
6
|
+
|
|
7
|
+
from ..context import WorkspaceContext
|
|
8
|
+
from ..envs import install_environment
|
|
9
|
+
from ..lockfile import generate_lockfile, install_from_lockfile
|
|
10
|
+
from ..parsers import detect_and_parse
|
|
11
|
+
from ..resolver import resolve_all_environments, resolve_environment
|
|
12
|
+
|
|
13
|
+
if TYPE_CHECKING:
|
|
14
|
+
import argparse
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def execute_install(args: argparse.Namespace) -> int:
|
|
18
|
+
"""Install (create/update) workspace environments."""
|
|
19
|
+
manifest_path = getattr(args, "file", None)
|
|
20
|
+
_, config = detect_and_parse(manifest_path)
|
|
21
|
+
ctx = WorkspaceContext(config)
|
|
22
|
+
|
|
23
|
+
env_name = getattr(args, "environment", None)
|
|
24
|
+
force = getattr(args, "force_reinstall", False)
|
|
25
|
+
dry_run = getattr(args, "dry_run", False)
|
|
26
|
+
locked = getattr(args, "locked", False)
|
|
27
|
+
|
|
28
|
+
if locked:
|
|
29
|
+
return _install_locked(ctx, config, env_name)
|
|
30
|
+
|
|
31
|
+
if env_name:
|
|
32
|
+
resolved = resolve_environment(config, env_name, ctx.platform)
|
|
33
|
+
print(f"Installing environment '{env_name}'...")
|
|
34
|
+
install_environment(ctx, resolved, force_reinstall=force, dry_run=dry_run)
|
|
35
|
+
if not dry_run:
|
|
36
|
+
generate_lockfile(ctx, env_names=[env_name])
|
|
37
|
+
print(f"Environment '{env_name}' is ready at {ctx.env_prefix(env_name)}")
|
|
38
|
+
else:
|
|
39
|
+
resolved_all = resolve_all_environments(config, ctx.platform)
|
|
40
|
+
installed_names: list[str] = []
|
|
41
|
+
for name, resolved in resolved_all.items():
|
|
42
|
+
print(f"Installing environment '{name}'...")
|
|
43
|
+
install_environment(ctx, resolved, force_reinstall=force, dry_run=dry_run)
|
|
44
|
+
installed_names.append(name)
|
|
45
|
+
print(f" -> {ctx.env_prefix(name)}")
|
|
46
|
+
if not dry_run:
|
|
47
|
+
generate_lockfile(ctx, env_names=installed_names)
|
|
48
|
+
print(f"\n{len(resolved_all)} environment(s) installed.")
|
|
49
|
+
|
|
50
|
+
return 0
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def _install_locked(ctx: WorkspaceContext, config: object, env_name: str | None) -> int:
|
|
54
|
+
"""Install environments from existing lockfiles (no solving)."""
|
|
55
|
+
if env_name:
|
|
56
|
+
print(f"Installing environment '{env_name}' from lockfile...")
|
|
57
|
+
install_from_lockfile(ctx, env_name)
|
|
58
|
+
print(f"Environment '{env_name}' is ready at {ctx.env_prefix(env_name)}")
|
|
59
|
+
else:
|
|
60
|
+
resolved_all = resolve_all_environments(config, ctx.platform)
|
|
61
|
+
for name in resolved_all:
|
|
62
|
+
print(f"Installing environment '{name}' from lockfile...")
|
|
63
|
+
install_from_lockfile(ctx, name)
|
|
64
|
+
print(f" -> {ctx.env_prefix(name)}")
|
|
65
|
+
print(f"\n{len(resolved_all)} environment(s) installed from lockfiles.")
|
|
66
|
+
|
|
67
|
+
return 0
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
"""``conda workspace list`` — list workspace environments."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import json
|
|
6
|
+
from typing import TYPE_CHECKING
|
|
7
|
+
|
|
8
|
+
from ..context import WorkspaceContext
|
|
9
|
+
from ..envs import list_installed_environments
|
|
10
|
+
from ..parsers import detect_and_parse
|
|
11
|
+
|
|
12
|
+
if TYPE_CHECKING:
|
|
13
|
+
import argparse
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def execute_list(args: argparse.Namespace) -> int:
|
|
17
|
+
"""List environments defined in the workspace."""
|
|
18
|
+
manifest_path = getattr(args, "file", None)
|
|
19
|
+
_, config = detect_and_parse(manifest_path)
|
|
20
|
+
ctx = WorkspaceContext(config)
|
|
21
|
+
|
|
22
|
+
installed_only = getattr(args, "installed", False)
|
|
23
|
+
json_output = getattr(args, "json", False)
|
|
24
|
+
|
|
25
|
+
installed = set(list_installed_environments(ctx))
|
|
26
|
+
|
|
27
|
+
rows: list[dict[str, str | bool | list[str]]] = []
|
|
28
|
+
for name, env in sorted(config.environments.items()):
|
|
29
|
+
if installed_only and name not in installed:
|
|
30
|
+
continue
|
|
31
|
+
rows.append(
|
|
32
|
+
{
|
|
33
|
+
"name": name,
|
|
34
|
+
"features": env.features,
|
|
35
|
+
"solve_group": env.solve_group or "",
|
|
36
|
+
"installed": name in installed,
|
|
37
|
+
}
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
if json_output:
|
|
41
|
+
print(json.dumps(rows, indent=2))
|
|
42
|
+
else:
|
|
43
|
+
if not rows:
|
|
44
|
+
print("No environments found.")
|
|
45
|
+
return 0
|
|
46
|
+
|
|
47
|
+
# Header
|
|
48
|
+
print(f"{'Name':<20} {'Features':<30} {'Solve Group':<15} {'Installed'}")
|
|
49
|
+
print("-" * 75)
|
|
50
|
+
for row in rows:
|
|
51
|
+
feats = ", ".join(row["features"]) if row["features"] else "(default)" # type: ignore[arg-type]
|
|
52
|
+
status = "yes" if row["installed"] else "no"
|
|
53
|
+
print(f"{row['name']:<20} {feats:<30} {row['solve_group']:<15} {status}")
|
|
54
|
+
|
|
55
|
+
return 0
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
"""``conda workspace lock`` — generate lockfiles for workspace environments."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import TYPE_CHECKING
|
|
6
|
+
|
|
7
|
+
from ..context import WorkspaceContext
|
|
8
|
+
from ..lockfile import generate_lockfile, lockfile_path
|
|
9
|
+
from ..parsers import detect_and_parse
|
|
10
|
+
from ..resolver import resolve_all_environments
|
|
11
|
+
|
|
12
|
+
if TYPE_CHECKING:
|
|
13
|
+
import argparse
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def execute_lock(args: argparse.Namespace) -> int:
|
|
17
|
+
"""Generate a ``conda.lock`` for installed workspace environments."""
|
|
18
|
+
manifest_path = getattr(args, "file", None)
|
|
19
|
+
_, config = detect_and_parse(manifest_path)
|
|
20
|
+
ctx = WorkspaceContext(config)
|
|
21
|
+
|
|
22
|
+
env_name = getattr(args, "environment", None)
|
|
23
|
+
|
|
24
|
+
if env_name:
|
|
25
|
+
if not ctx.env_exists(env_name):
|
|
26
|
+
from ..exceptions import EnvironmentNotInstalledError
|
|
27
|
+
|
|
28
|
+
raise EnvironmentNotInstalledError(env_name)
|
|
29
|
+
path = generate_lockfile(ctx, env_names=[env_name])
|
|
30
|
+
print(f"Lockfile written to {path}")
|
|
31
|
+
else:
|
|
32
|
+
resolved_all = resolve_all_environments(config, ctx.platform)
|
|
33
|
+
installed = [name for name in resolved_all if ctx.env_exists(name)]
|
|
34
|
+
skipped = [name for name in resolved_all if not ctx.env_exists(name)]
|
|
35
|
+
if installed:
|
|
36
|
+
path = generate_lockfile(ctx, env_names=installed)
|
|
37
|
+
for name in installed:
|
|
38
|
+
print(f" {name} -> {path}")
|
|
39
|
+
for name in skipped:
|
|
40
|
+
print(f" {name} — skipped (not installed)")
|
|
41
|
+
print(f"\n{len(installed)} environment(s) locked in {lockfile_path(ctx)}.")
|
|
42
|
+
|
|
43
|
+
return 0
|