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.
- conda_features-0.1.0/.gitignore +10 -0
- conda_features-0.1.0/LICENSE +29 -0
- conda_features-0.1.0/PKG-INFO +84 -0
- conda_features-0.1.0/README.md +70 -0
- conda_features-0.1.0/pyproject.toml +94 -0
- conda_features-0.1.0/src/conda_features/__init__.py +75 -0
- conda_features-0.1.0/src/conda_features/cli/__init__.py +1 -0
- conda_features-0.1.0/src/conda_features/cli/apply.py +26 -0
- conda_features-0.1.0/src/conda_features/cli/cleanup.py +16 -0
- conda_features-0.1.0/src/conda_features/cli/commands.py +35 -0
- conda_features-0.1.0/src/conda_features/cli/common.py +90 -0
- conda_features-0.1.0/src/conda_features/cli/configure.py +22 -0
- conda_features-0.1.0/src/conda_features/cli/diagnose.py +13 -0
- conda_features-0.1.0/src/conda_features/cli/disable.py +44 -0
- conda_features-0.1.0/src/conda_features/cli/enable.py +22 -0
- conda_features-0.1.0/src/conda_features/cli/explain.py +11 -0
- conda_features-0.1.0/src/conda_features/cli/export.py +26 -0
- conda_features-0.1.0/src/conda_features/cli/list.py +14 -0
- conda_features-0.1.0/src/conda_features/cli/migrate.py +16 -0
- conda_features-0.1.0/src/conda_features/cli/output.py +74 -0
- conda_features-0.1.0/src/conda_features/cli/parser.py +50 -0
- conda_features-0.1.0/src/conda_features/cli/repair.py +16 -0
- conda_features-0.1.0/src/conda_features/cli/targets.py +28 -0
- conda_features-0.1.0/src/conda_features/hooks.py +22 -0
- conda_features-0.1.0/src/conda_features/models.py +291 -0
- conda_features-0.1.0/src/conda_features/operations/__init__.py +1 -0
- conda_features-0.1.0/src/conda_features/operations/configuration.py +193 -0
- conda_features-0.1.0/src/conda_features/operations/dependencies.py +256 -0
- conda_features-0.1.0/src/conda_features/operations/diagnostics.py +113 -0
- conda_features-0.1.0/src/conda_features/operations/discovery.py +45 -0
- conda_features-0.1.0/src/conda_features/operations/doctor.py +64 -0
- conda_features-0.1.0/src/conda_features/operations/intent.py +250 -0
- conda_features-0.1.0/src/conda_features/operations/lifecycle.py +146 -0
- conda_features-0.1.0/src/conda_features/operations/settings.py +101 -0
- conda_features-0.1.0/src/conda_features/operations/validation.py +120 -0
- conda_features-0.1.0/src/conda_features/plugin.py +51 -0
- conda_features-0.1.0/src/conda_features/steps/__init__.py +7 -0
- conda_features-0.1.0/src/conda_features/steps/base.py +41 -0
- conda_features-0.1.0/src/conda_features/steps/block.py +98 -0
- conda_features-0.1.0/src/conda_features/steps/configuration.py +222 -0
- conda_features-0.1.0/src/conda_features/steps/packages.py +107 -0
- conda_features-0.1.0/src/conda_features/steps/runner.py +162 -0
- conda_features-0.1.0/src/conda_features/steps/storage.py +85 -0
|
@@ -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)
|