conda-features 0.1.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 (43) hide show
  1. conda_features-0.1.0/.gitignore +10 -0
  2. conda_features-0.1.0/LICENSE +29 -0
  3. conda_features-0.1.0/PKG-INFO +84 -0
  4. conda_features-0.1.0/README.md +70 -0
  5. conda_features-0.1.0/pyproject.toml +94 -0
  6. conda_features-0.1.0/src/conda_features/__init__.py +75 -0
  7. conda_features-0.1.0/src/conda_features/cli/__init__.py +1 -0
  8. conda_features-0.1.0/src/conda_features/cli/apply.py +26 -0
  9. conda_features-0.1.0/src/conda_features/cli/cleanup.py +16 -0
  10. conda_features-0.1.0/src/conda_features/cli/commands.py +35 -0
  11. conda_features-0.1.0/src/conda_features/cli/common.py +90 -0
  12. conda_features-0.1.0/src/conda_features/cli/configure.py +22 -0
  13. conda_features-0.1.0/src/conda_features/cli/diagnose.py +13 -0
  14. conda_features-0.1.0/src/conda_features/cli/disable.py +44 -0
  15. conda_features-0.1.0/src/conda_features/cli/enable.py +22 -0
  16. conda_features-0.1.0/src/conda_features/cli/explain.py +11 -0
  17. conda_features-0.1.0/src/conda_features/cli/export.py +26 -0
  18. conda_features-0.1.0/src/conda_features/cli/list.py +14 -0
  19. conda_features-0.1.0/src/conda_features/cli/migrate.py +16 -0
  20. conda_features-0.1.0/src/conda_features/cli/output.py +74 -0
  21. conda_features-0.1.0/src/conda_features/cli/parser.py +50 -0
  22. conda_features-0.1.0/src/conda_features/cli/repair.py +16 -0
  23. conda_features-0.1.0/src/conda_features/cli/targets.py +28 -0
  24. conda_features-0.1.0/src/conda_features/hooks.py +22 -0
  25. conda_features-0.1.0/src/conda_features/models.py +291 -0
  26. conda_features-0.1.0/src/conda_features/operations/__init__.py +1 -0
  27. conda_features-0.1.0/src/conda_features/operations/configuration.py +193 -0
  28. conda_features-0.1.0/src/conda_features/operations/dependencies.py +256 -0
  29. conda_features-0.1.0/src/conda_features/operations/diagnostics.py +113 -0
  30. conda_features-0.1.0/src/conda_features/operations/discovery.py +45 -0
  31. conda_features-0.1.0/src/conda_features/operations/doctor.py +64 -0
  32. conda_features-0.1.0/src/conda_features/operations/intent.py +250 -0
  33. conda_features-0.1.0/src/conda_features/operations/lifecycle.py +146 -0
  34. conda_features-0.1.0/src/conda_features/operations/settings.py +101 -0
  35. conda_features-0.1.0/src/conda_features/operations/validation.py +120 -0
  36. conda_features-0.1.0/src/conda_features/plugin.py +51 -0
  37. conda_features-0.1.0/src/conda_features/steps/__init__.py +7 -0
  38. conda_features-0.1.0/src/conda_features/steps/base.py +41 -0
  39. conda_features-0.1.0/src/conda_features/steps/block.py +98 -0
  40. conda_features-0.1.0/src/conda_features/steps/configuration.py +222 -0
  41. conda_features-0.1.0/src/conda_features/steps/packages.py +107 -0
  42. conda_features-0.1.0/src/conda_features/steps/runner.py +162 -0
  43. conda_features-0.1.0/src/conda_features/steps/storage.py +85 -0
@@ -0,0 +1,10 @@
1
+ .DS_Store
2
+ .pixi/
3
+ .pytest_cache/
4
+ .ruff_cache/
5
+ __pycache__/
6
+ *.py[cod]
7
+ dist/
8
+ conda-dist/
9
+ docs/_build/
10
+ *.egg-info/
@@ -0,0 +1,29 @@
1
+ BSD 3-Clause License
2
+
3
+ Copyright (c) 2026, conda-features contributors
4
+ All rights reserved.
5
+
6
+ Redistribution and use in source and binary forms, with or without
7
+ modification, are permitted provided that the following conditions are met:
8
+
9
+ 1. Redistributions of source code must retain the above copyright notice, this
10
+ list of conditions and the following disclaimer.
11
+
12
+ 2. Redistributions in binary form must reproduce the above copyright notice,
13
+ this list of conditions and the following disclaimer in the documentation
14
+ and/or other materials provided with the distribution.
15
+
16
+ 3. Neither the name of the copyright holder nor the names of its contributors
17
+ may be used to endorse or promote products derived from this software
18
+ without specific prior written permission.
19
+
20
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
21
+ AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
22
+ IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
23
+ DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
24
+ FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
25
+ DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
26
+ SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
27
+ CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
28
+ OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
29
+ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
@@ -0,0 +1,84 @@
1
+ Metadata-Version: 2.5
2
+ Name: conda-features
3
+ Version: 0.1.0
4
+ Summary: Discover and configure optional features from installed conda plugins
5
+ Project-URL: Homepage, https://github.com/jezdez/conda-features
6
+ Project-URL: Documentation, https://jezdez.github.io/conda-features/
7
+ Project-URL: Issues, https://github.com/jezdez/conda-features/issues
8
+ License-Expression: BSD-3-Clause
9
+ License-File: LICENSE
10
+ Requires-Python: >=3.12
11
+ Requires-Dist: filelock>=3.16
12
+ Requires-Dist: pluggy>=1.5
13
+ Description-Content-Type: text/markdown
14
+
15
+ # conda-features
16
+
17
+ A conda plugin for discovering and managing optional features supplied by installed providers. Inspect saved choices and readiness, preview changes, deploy prerequisites, and recover interrupted setup without claiming to observe provider execution.
18
+
19
+ The core package ships no feature catalog. The separate `conda-features-examples` distribution provides real PyPI and shell-completion deployments. These can install packages and change configuration or shell files after confirmation.
20
+
21
+ ## Install and use
22
+
23
+ Use Python 3.12 or newer with conda 26.9.4 or newer. Install into the Python environment running conda, not just an environment you want to configure. These packages are not published on PyPI yet. The current installation method is from Git, which requires Git and pip:
24
+
25
+ ```sh
26
+ python -m conda --version
27
+ python -m pip install "conda-features @ git+https://github.com/jezdez/conda-features.git@main"
28
+ python -m pip install "conda-features-examples @ git+https://github.com/jezdez/conda-features.git@main#subdirectory=packages/conda-features-examples"
29
+ ```
30
+
31
+ Use that installation's conda command:
32
+
33
+ ```sh
34
+ conda feature list
35
+ conda feature explain conda-pypi
36
+ conda feature enable conda-pypi --user --dry-run
37
+ ```
38
+
39
+ The first distribution supplies the command. The second supplies real deployment declarations. Enabling a feature can then install missing upstream prerequisites after confirmation. See [Installation](docs/how-to/install.md) for choosing the correct host interpreter and [the tutorial](docs/tutorials/first-deployment.md) for a complete preview, enable, and disable sequence.
40
+
41
+ ## Documentation
42
+
43
+ The documentation is published to [GitHub Pages](https://jezdez.github.io/conda-features/) from `main`. Pull requests build the site without deploying it.
44
+
45
+ Start with the [documentation index](docs/index.md) or the [first deployment tutorial](docs/tutorials/first-deployment.md), which uses your installed plugin and a temporary configuration file.
46
+
47
+ The Sphinx documentation follows Diátaxis: tutorials, how-to guides, reference, and explanation. Contributor setup and documentation build commands are in [Development](docs/how-to/develop.md).
48
+
49
+ - [Manage features and scopes](docs/how-to/manage-features.md)
50
+ - [Deploy real PyPI support and shell completion](docs/how-to/deploy-providers.md)
51
+ - [Diagnose and recover setup](docs/how-to/recover.md)
52
+ - [Migrate and transfer choices](docs/how-to/transfer.md)
53
+ - [Build your first provider](docs/tutorials/create-provider.md)
54
+ - [Command reference](docs/reference/cli.md)
55
+ - [Provider and step API](docs/reference/providers.md)
56
+ - [Portable intent format](docs/reference/intent.md)
57
+ - [Configuration, ownership, and recovery explained](docs/explanation/deployment.md)
58
+ - [Development and verification](docs/how-to/develop.md)
59
+
60
+ The [design document](DESIGN.md) includes goals beyond the current implementation.
61
+
62
+ ## Commands at a glance
63
+
64
+ ```text
65
+ conda feature list
66
+ conda feature explain FEATURE
67
+ conda feature enable FEATURE --dry-run
68
+ conda feature configure FEATURE KEY=VALUE --dry-run
69
+ conda feature disable FEATURE --dry-run
70
+ conda feature diagnose --json
71
+ conda feature repair FEATURE --dry-run
72
+ conda feature migrate FEATURE --dry-run
73
+ conda feature cleanup FEATURE --dry-run
74
+ conda feature export --file source.yaml
75
+ conda feature apply intent.json --file destination.yaml --dry-run
76
+ ```
77
+
78
+ Use an installed feature ID in place of `FEATURE`. Mutations require confirmation or `--yes`. Deployment dry-runs may solve packages and refresh caches without installing packages or writing managed resources. `--file` isolates conda configuration, not package installation or shell files.
79
+
80
+ ## Current limits
81
+
82
+ Authentication, telemetry, remote catalogs, and automatic migration are not implemented. Environment checks cover explicit targets and the current user's registry, not a complete machine inventory. Runtime activity remains unknown. Generic package and shell migrations need provider-specific handling, and deployment intent is applied one feature at a time.
83
+
84
+ CI is configured for Linux x64, Windows x64, and ARM macOS core tests, with real-provider integration on Linux. See the relevant workflow results for the revision you use rather than assuming every newer conda version or shell has been verified.
@@ -0,0 +1,70 @@
1
+ # conda-features
2
+
3
+ A conda plugin for discovering and managing optional features supplied by installed providers. Inspect saved choices and readiness, preview changes, deploy prerequisites, and recover interrupted setup without claiming to observe provider execution.
4
+
5
+ The core package ships no feature catalog. The separate `conda-features-examples` distribution provides real PyPI and shell-completion deployments. These can install packages and change configuration or shell files after confirmation.
6
+
7
+ ## Install and use
8
+
9
+ Use Python 3.12 or newer with conda 26.9.4 or newer. Install into the Python environment running conda, not just an environment you want to configure. These packages are not published on PyPI yet. The current installation method is from Git, which requires Git and pip:
10
+
11
+ ```sh
12
+ python -m conda --version
13
+ python -m pip install "conda-features @ git+https://github.com/jezdez/conda-features.git@main"
14
+ python -m pip install "conda-features-examples @ git+https://github.com/jezdez/conda-features.git@main#subdirectory=packages/conda-features-examples"
15
+ ```
16
+
17
+ Use that installation's conda command:
18
+
19
+ ```sh
20
+ conda feature list
21
+ conda feature explain conda-pypi
22
+ conda feature enable conda-pypi --user --dry-run
23
+ ```
24
+
25
+ The first distribution supplies the command. The second supplies real deployment declarations. Enabling a feature can then install missing upstream prerequisites after confirmation. See [Installation](docs/how-to/install.md) for choosing the correct host interpreter and [the tutorial](docs/tutorials/first-deployment.md) for a complete preview, enable, and disable sequence.
26
+
27
+ ## Documentation
28
+
29
+ The documentation is published to [GitHub Pages](https://jezdez.github.io/conda-features/) from `main`. Pull requests build the site without deploying it.
30
+
31
+ Start with the [documentation index](docs/index.md) or the [first deployment tutorial](docs/tutorials/first-deployment.md), which uses your installed plugin and a temporary configuration file.
32
+
33
+ The Sphinx documentation follows Diátaxis: tutorials, how-to guides, reference, and explanation. Contributor setup and documentation build commands are in [Development](docs/how-to/develop.md).
34
+
35
+ - [Manage features and scopes](docs/how-to/manage-features.md)
36
+ - [Deploy real PyPI support and shell completion](docs/how-to/deploy-providers.md)
37
+ - [Diagnose and recover setup](docs/how-to/recover.md)
38
+ - [Migrate and transfer choices](docs/how-to/transfer.md)
39
+ - [Build your first provider](docs/tutorials/create-provider.md)
40
+ - [Command reference](docs/reference/cli.md)
41
+ - [Provider and step API](docs/reference/providers.md)
42
+ - [Portable intent format](docs/reference/intent.md)
43
+ - [Configuration, ownership, and recovery explained](docs/explanation/deployment.md)
44
+ - [Development and verification](docs/how-to/develop.md)
45
+
46
+ The [design document](DESIGN.md) includes goals beyond the current implementation.
47
+
48
+ ## Commands at a glance
49
+
50
+ ```text
51
+ conda feature list
52
+ conda feature explain FEATURE
53
+ conda feature enable FEATURE --dry-run
54
+ conda feature configure FEATURE KEY=VALUE --dry-run
55
+ conda feature disable FEATURE --dry-run
56
+ conda feature diagnose --json
57
+ conda feature repair FEATURE --dry-run
58
+ conda feature migrate FEATURE --dry-run
59
+ conda feature cleanup FEATURE --dry-run
60
+ conda feature export --file source.yaml
61
+ conda feature apply intent.json --file destination.yaml --dry-run
62
+ ```
63
+
64
+ Use an installed feature ID in place of `FEATURE`. Mutations require confirmation or `--yes`. Deployment dry-runs may solve packages and refresh caches without installing packages or writing managed resources. `--file` isolates conda configuration, not package installation or shell files.
65
+
66
+ ## Current limits
67
+
68
+ Authentication, telemetry, remote catalogs, and automatic migration are not implemented. Environment checks cover explicit targets and the current user's registry, not a complete machine inventory. Runtime activity remains unknown. Generic package and shell migrations need provider-specific handling, and deployment intent is applied one feature at a time.
69
+
70
+ CI is configured for Linux x64, Windows x64, and ARM macOS core tests, with real-provider integration on Linux. See the relevant workflow results for the revision you use rather than assuming every newer conda version or shell has been verified.
@@ -0,0 +1,94 @@
1
+ [build-system]
2
+ requires = ["hatchling>=1.27"]
3
+ build-backend = "hatchling.build"
4
+
5
+ [project]
6
+ name = "conda-features"
7
+ version = "0.1.0"
8
+ description = "Discover and configure optional features from installed conda plugins"
9
+ readme = "README.md"
10
+ license = "BSD-3-Clause"
11
+ license-files = ["LICENSE"]
12
+ requires-python = ">=3.12"
13
+ dependencies = ["filelock>=3.16", "pluggy>=1.5"]
14
+
15
+ [tool.hatch.build.targets.sdist]
16
+ include = ["/src", "/README.md", "/LICENSE", "/pyproject.toml"]
17
+
18
+ [project.urls]
19
+ Homepage = "https://github.com/jezdez/conda-features"
20
+ Documentation = "https://jezdez.github.io/conda-features/"
21
+ Issues = "https://github.com/jezdez/conda-features/issues"
22
+
23
+ [project.entry-points.conda]
24
+ conda-features = "conda_features.plugin"
25
+
26
+ [tool.pixi.workspace]
27
+ channels = ["conda-forge"]
28
+ platforms = ["osx-arm64", "linux-64", "win-64"]
29
+
30
+ [tool.pixi.dependencies]
31
+ python = "3.12.*"
32
+ conda = "26.9.4.*"
33
+ filelock = ">=3.16"
34
+ pluggy = ">=1.5"
35
+ pytest = ">=8"
36
+ ruff = ">=0.14"
37
+ ty = "*"
38
+ pip = "*"
39
+ hatchling = "*"
40
+
41
+ [tool.pixi.pypi-dependencies]
42
+ conda-features = { path = ".", editable = true }
43
+
44
+ [tool.pixi.tasks]
45
+ test = "pytest"
46
+ lint = "ruff check ."
47
+ format-check = "ruff format --check ."
48
+ type-check = "ty check src"
49
+
50
+ [tool.pixi.feature.examples.pypi-dependencies]
51
+ conda-features-examples = { path = "packages/conda-features-examples", editable = true }
52
+
53
+ [tool.pixi.feature.examples.tasks]
54
+ test-examples = "pytest tests/test_examples.py"
55
+
56
+ [tool.pixi.feature.integration.dependencies]
57
+ conda-pypi = "0.13.*"
58
+ conda-rattler-solver = "26.10.*"
59
+ conda-completion = "0.3.2.*"
60
+
61
+ [tool.pixi.feature.integration.tasks]
62
+ test-integration = "pytest tests/test_examples.py"
63
+
64
+ [tool.pixi.feature.docs.dependencies]
65
+ sphinx = ">=8"
66
+ myst-parser = ">=4"
67
+ conda-sphinx-theme = ">=0.3"
68
+ sphinx-design = ">=0.6"
69
+
70
+ [tool.pixi.feature.docs.tasks]
71
+ docs = "sphinx-build -W --keep-going -n -b dirhtml docs docs/_build/dirhtml"
72
+
73
+ [tool.pixi.feature.release.dependencies]
74
+ python = "3.12.*"
75
+ python-build = ">=1.2"
76
+ hatchling = ">=1.27"
77
+ twine = ">=6.2"
78
+ rattler-build = ">=0.74"
79
+
80
+ [tool.pixi.environments]
81
+ default = { features = [], solve-group = "default" }
82
+ examples = { features = ["examples"], solve-group = "default" }
83
+ integration = { features = ["examples", "integration"] }
84
+ docs = { features = ["docs"], solve-group = "default" }
85
+ release = { features = ["release"], no-default-feature = true }
86
+
87
+ [tool.pytest.ini_options]
88
+ testpaths = ["tests"]
89
+
90
+ [tool.ruff]
91
+ target-version = "py312"
92
+
93
+ [tool.ruff.lint]
94
+ select = ["E4", "E7", "E9", "F", "I", "UP"]
@@ -0,0 +1,75 @@
1
+ """Feature management for conda.
2
+
3
+ Install this distribution in the Python environment running conda. During
4
+ development, ``pixi run python -m conda feature --help`` uses that environment.
5
+
6
+ User commands::
7
+
8
+ conda feature list
9
+ conda feature explain sample
10
+ conda feature enable sample --dry-run
11
+ conda feature enable sample --yes
12
+ conda feature configure sample plugins.sample_limit=4 --yes
13
+ conda feature disable sample --yes
14
+ conda feature disable --all --yes
15
+
16
+ Use ``--prefix PATH`` for an existing environment, ``--system`` for the conda
17
+ installation, or ``--user`` (the default). Providers declare supported scopes.
18
+ ``--file PATH`` selects a standalone user-scope file, with environment-variable
19
+ overrides still applied. Use a conda-recognized name (``.condarc``, ``condarc``,
20
+ or a YAML extension). Its parent directory must exist. Other scopes use
21
+ conda's normal configuration search path. ``--json`` produces one JSON result.
22
+ Exit codes are 0 for success or preview, 1 for an operation error, and 2 for
23
+ argument parsing errors. Noninteractive mutations require ``--yes``.
24
+
25
+ Providers register their settings with conda's ``conda_settings`` hook and
26
+ yield ``conda_features.models.Feature`` objects from a Pluggy hook. Register
27
+ the provider module in their pyproject.toml::
28
+
29
+ [project.entry-points.conda_features]
30
+ my-provider = "my_provider.features"
31
+
32
+ An example declaration::
33
+
34
+ from conda_features.models import Feature
35
+ from conda_features.hooks import hookimpl
36
+
37
+ @hookimpl
38
+ def conda_features():
39
+ yield Feature(
40
+ name="sample",
41
+ description="Enable the provider's optional behavior",
42
+ enabled_setting="plugins.sample_enabled",
43
+ settings=("plugins.sample_limit",),
44
+ )
45
+
46
+ The provider reads its boolean setting to control behavior. This plugin does
47
+ not intercept provider execution. Declarations must be cheap to import and
48
+ must not access the network, credentials, or files. Installed providers are
49
+ trusted Python code, not sandboxed data. Only expose non-secret settings.
50
+ Required settings check for missing values, not login or service availability.
51
+ Status reports the selected file's saved choice separately from the effective
52
+ boolean setting and readiness. Runtime activity is unknown, even when a setting
53
+ is enabled and a prerequisite is blocked. Providers enforce their own checks.
54
+
55
+ Enable refuses unmet dependencies instead of implicitly opting into them.
56
+ Disable preserves other settings and installed packages. Changes take effect
57
+ on the next invocation. Repeating a persisted operation requires no write.
58
+ An explicit disable is saved even if a temporary override already disables it.
59
+ Writes preserve comments and unrelated settings, use atomic file replacement,
60
+ and reject previews whose destination changed. A sidecar lock coordinates this
61
+ plugin's writers, but external editors do not honor it.
62
+
63
+ Deployment providers can omit enabled_setting and declare steps such as
64
+ InstallPackages, SetSetting, and EnsureChannel. Feature coordinates their
65
+ planning, application, reverse-order disable, and recovery receipts.
66
+ Provider-specific steps implement Step or ManagedBlock. Plans carry public
67
+ changes and private apply data. The CLI confirms before applying. The separate
68
+ conda-features-examples package deploys real PyPI support and shell completion,
69
+ including installation of missing prerequisites. Bulk disable handles boolean
70
+ features and identifies deployments that need individual disable commands.
71
+
72
+ Without an installed provider, list reports an empty catalog. Authentication,
73
+ cross-environment dependency scanning, telemetry, migrations, export, and
74
+ doctor integration are not implemented.
75
+ """
@@ -0,0 +1 @@
1
+ """Command-line parsing, dispatch, and rendering."""
@@ -0,0 +1,26 @@
1
+ """Apply a portable intent document to an explicitly selected destination."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import json
6
+ from pathlib import Path
7
+
8
+
9
+ def configure_parser(parser):
10
+ parser.add_argument("manifest", type=Path)
11
+
12
+
13
+ def execute(args, catalog, ctx, path, scope):
14
+ from ..operations.intent import IntentError, plan_intent
15
+ from .common import confirm_and_apply
16
+
17
+ if not (args.user or args.system or args.prefix or args.file):
18
+ raise IntentError(
19
+ "Applying intent requires an explicit --user, --system, --prefix, or --file destination."
20
+ )
21
+ try:
22
+ document = json.loads(args.manifest.read_text())
23
+ except (OSError, ValueError) as exc:
24
+ raise IntentError("Cannot read feature intent JSON.") from exc
25
+ plan = plan_intent(document, catalog, ctx, path, scope)
26
+ return confirm_and_apply(args, catalog, ctx, plan)
@@ -0,0 +1,16 @@
1
+ """Remove obsolete opt-ins for unconditional graduated features."""
2
+
3
+ from __future__ import annotations
4
+
5
+
6
+ def configure_parser(parser):
7
+ parser.add_argument("feature")
8
+
9
+
10
+ def execute(args, catalog, ctx, path, scope):
11
+ from .common import confirm_and_apply, select_feature
12
+
13
+ feature = select_feature(catalog, args.feature)
14
+ return confirm_and_apply(
15
+ args, catalog, ctx, feature.cleanup(catalog, ctx, path, scope)
16
+ )
@@ -0,0 +1,35 @@
1
+ """Load the selected subcommand and render its result."""
2
+
3
+ import json
4
+ import sys
5
+ from argparse import Namespace
6
+ from importlib import import_module
7
+
8
+ from conda import CondaError
9
+ from conda.base.context import context
10
+
11
+ from ..operations.discovery import discover
12
+ from ..operations.intent import IntentError
13
+ from ..operations.validation import validate_catalog
14
+ from .output import render
15
+ from .targets import target
16
+
17
+
18
+ def execute(args: Namespace) -> int:
19
+ try:
20
+ catalog = discover()
21
+ path, scope, ctx = target(args)
22
+ validate_catalog(catalog, ctx)
23
+ command = import_module(f".{args.operation}", package=__package__)
24
+ result = command.execute(args, catalog, ctx, path, scope)
25
+ except (CondaError, OSError, ValueError, TypeError, OverflowError) as exc:
26
+ message = str(exc)
27
+ if args.operation in ("export", "apply") and not isinstance(exc, IntentError):
28
+ message = "Intent operation failed. Inspect the local configuration and providers. Review with --dry-run and pass --yes only after approving the changes. Configuration values are omitted from this error."
29
+ if context.json:
30
+ print(json.dumps({"error": message}))
31
+ else:
32
+ print(message, file=sys.stderr)
33
+ return 1
34
+ render(result)
35
+ return 0
@@ -0,0 +1,90 @@
1
+ """Shared feature selection and confirmed configuration writes."""
2
+
3
+ import sys
4
+ from pathlib import Path
5
+
6
+ from conda import CondaError
7
+ from conda.base.context import context
8
+ from conda.common.configuration import YamlRawParameter
9
+
10
+ from ..models import Catalog, Feature
11
+ from ..operations.configuration import apply_change
12
+ from ..operations.dependencies import guard_plan, projected_context, verify_guard
13
+ from ..operations.settings import fingerprint
14
+ from .targets import target
15
+
16
+
17
+ def select_feature(catalog: Catalog, name: str) -> Feature:
18
+ if name not in catalog:
19
+ raise CondaError(f"Unknown feature: {name}")
20
+ return catalog[name][1]
21
+
22
+
23
+ def confirm_and_apply(args, catalog, ctx, plan):
24
+ guard_plan(args, catalog, plan)
25
+ result = {
26
+ key: plan[key]
27
+ for key in (
28
+ "operation",
29
+ "features",
30
+ "scope",
31
+ "path",
32
+ "changes",
33
+ "checked_environments",
34
+ "discovery",
35
+ "remove",
36
+ )
37
+ if key in plan
38
+ }
39
+ if args.dry_run:
40
+ result["message"] = "Preview only. No changes made."
41
+ elif not plan["changes"]:
42
+ result["message"] = "No changes needed."
43
+ else:
44
+ if not context.always_yes:
45
+ if context.json or not sys.stdin.isatty():
46
+ raise CondaError(
47
+ "Review with --dry-run, then pass --yes to apply noninteractively."
48
+ )
49
+ print(f"Configuration: {plan['path']} ({plan['scope']})", file=sys.stderr)
50
+ if plan["checked_environments"]:
51
+ print(
52
+ "Checked environments: " + ", ".join(plan["checked_environments"]),
53
+ file=sys.stderr,
54
+ )
55
+ print(plan["discovery"], file=sys.stderr)
56
+ for key, new_value in plan["changes"].items():
57
+ print(f" {key} = {new_value}", file=sys.stderr)
58
+ print("Proceed? [y/N] ", end="", file=sys.stderr, flush=True)
59
+ if sys.stdin.readline().strip().lower() not in ("y", "yes"):
60
+ raise CondaError("Cancelled. No changes made.")
61
+ verify_guard(plan)
62
+ if "plans" in plan:
63
+ current = ctx
64
+ expected = plan["fingerprint"]
65
+ for child in plan["plans"]:
66
+ if fingerprint(Path(child["path"])) != expected:
67
+ raise CondaError(
68
+ "Configuration changed after preview. Review the plan again."
69
+ )
70
+ child["fingerprint"] = expected
71
+ next_context = projected_context(child, current)
72
+ catalog[child["features"][-1]][1].apply(child, current)
73
+ expected = fingerprint(Path(child["path"]))
74
+ current = next_context
75
+ elif len(plan["features"]) == 1:
76
+ catalog[plan["features"][0]][1].apply(plan, ctx)
77
+ else:
78
+ apply_change(plan, ctx)
79
+ YamlRawParameter.cache_clear()
80
+ _, _, updated = target(args)
81
+ result["status"] = [
82
+ catalog[name][1].status(
83
+ catalog, updated, plan["scope"], path=Path(plan["path"])
84
+ )
85
+ for name in plan["features"]
86
+ ]
87
+ result["message"] = (
88
+ "Configuration updated. Takes effect in the next conda invocation."
89
+ )
90
+ return result
@@ -0,0 +1,22 @@
1
+ """Validate and change a feature's declared settings."""
2
+
3
+ from conda import CondaError
4
+
5
+
6
+ def configure_parser(parser):
7
+ parser.add_argument("feature")
8
+ parser.add_argument("settings", nargs="+", metavar="KEY=VALUE")
9
+
10
+
11
+ def execute(args, catalog, ctx, path, scope):
12
+ from .common import confirm_and_apply, select_feature
13
+
14
+ feature = select_feature(catalog, args.feature)
15
+ settings = {}
16
+ for item in args.settings:
17
+ key, separator, new_value = item.partition("=")
18
+ if not separator or not key or key in settings:
19
+ raise CondaError("Provide distinct declared settings as KEY=VALUE.")
20
+ settings[key] = new_value
21
+ plan = feature.configure(catalog, ctx, path, scope, settings)
22
+ return confirm_and_apply(args, catalog, ctx, plan)
@@ -0,0 +1,13 @@
1
+ """Inspect local feature readiness and retained deployment receipts."""
2
+
3
+ from __future__ import annotations
4
+
5
+
6
+ def configure_parser(parser):
7
+ pass
8
+
9
+
10
+ def execute(args, catalog, ctx, path, scope):
11
+ from ..operations.diagnostics import diagnose
12
+
13
+ return diagnose(catalog, ctx, path, scope)
@@ -0,0 +1,44 @@
1
+ """Preview and disable one feature or all features at a scope."""
2
+
3
+
4
+ def configure_parser(parser):
5
+ parser.add_argument("feature", nargs="?")
6
+ parser.add_argument(
7
+ "--all",
8
+ action="store_true",
9
+ help="Disable all declared features at this scope.",
10
+ )
11
+
12
+
13
+ def execute(args, catalog, ctx, path, scope):
14
+ from ..operations.configuration import plan_change
15
+ from .common import confirm_and_apply, select_feature
16
+
17
+ if args.all:
18
+ deployments = [
19
+ name
20
+ for name, (_, feature) in catalog.items()
21
+ if feature.enabled_setting is None and scope in feature.scopes
22
+ ]
23
+ boolean_catalog = {
24
+ name: item
25
+ for name, item in catalog.items()
26
+ if item[1].enabled_setting is not None
27
+ }
28
+ plan = plan_change(
29
+ boolean_catalog,
30
+ ctx,
31
+ path,
32
+ scope,
33
+ "disable",
34
+ args.feature,
35
+ all_features=True,
36
+ )
37
+ else:
38
+ plan = select_feature(catalog, args.feature).disable(catalog, ctx, path, scope)
39
+ result = confirm_and_apply(args, catalog, ctx, plan)
40
+ if args.all and deployments:
41
+ result["message"] += (
42
+ " Disable deployment features individually: " + ", ".join(deployments) + "."
43
+ )
44
+ return result
@@ -0,0 +1,22 @@
1
+ """Preview and enable an installed feature."""
2
+
3
+
4
+ def configure_parser(parser):
5
+ parser.add_argument("feature")
6
+ parser.add_argument(
7
+ "--with-dependencies",
8
+ action="store_true",
9
+ help="Preview prerequisite opt-ins at the selected scope.",
10
+ )
11
+
12
+
13
+ def execute(args, catalog, ctx, path, scope):
14
+ from .common import confirm_and_apply, select_feature
15
+
16
+ feature = select_feature(catalog, args.feature)
17
+ plan = (
18
+ feature.enable(catalog, ctx, path, scope, with_dependencies=True)
19
+ if args.with_dependencies
20
+ else feature.enable(catalog, ctx, path, scope)
21
+ )
22
+ return confirm_and_apply(args, catalog, ctx, plan)
@@ -0,0 +1,11 @@
1
+ """Explain a feature's configured state and prerequisites."""
2
+
3
+
4
+ def configure_parser(parser):
5
+ parser.add_argument("feature")
6
+
7
+
8
+ def execute(args, catalog, ctx, path, scope):
9
+ from .common import select_feature
10
+
11
+ return select_feature(catalog, args.feature).status(catalog, ctx, scope, path=path)