katalyst-taxonomy-cli 2.0.1a1__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.
- katalyst_taxonomy_cli-2.0.1a1/.gitignore +123 -0
- katalyst_taxonomy_cli-2.0.1a1/PKG-INFO +12 -0
- katalyst_taxonomy_cli-2.0.1a1/pyproject.toml +31 -0
- katalyst_taxonomy_cli-2.0.1a1/src/katalyst_taxonomy_cli/__init__.py +14 -0
- katalyst_taxonomy_cli-2.0.1a1/src/katalyst_taxonomy_cli/__main__.py +12 -0
- katalyst_taxonomy_cli-2.0.1a1/src/katalyst_taxonomy_cli/_wiring.py +248 -0
- katalyst_taxonomy_cli-2.0.1a1/src/katalyst_taxonomy_cli/app.py +550 -0
- katalyst_taxonomy_cli-2.0.1a1/src/katalyst_taxonomy_cli/commands/__init__.py +1 -0
- katalyst_taxonomy_cli-2.0.1a1/src/katalyst_taxonomy_cli/commands/api.py +104 -0
- katalyst_taxonomy_cli-2.0.1a1/src/katalyst_taxonomy_cli/commands/backend.py +96 -0
- katalyst_taxonomy_cli-2.0.1a1/src/katalyst_taxonomy_cli/commands/bundle.py +106 -0
- katalyst_taxonomy_cli-2.0.1a1/src/katalyst_taxonomy_cli/commands/clone.py +68 -0
- katalyst_taxonomy_cli-2.0.1a1/src/katalyst_taxonomy_cli/commands/config.py +748 -0
- katalyst_taxonomy_cli-2.0.1a1/src/katalyst_taxonomy_cli/commands/config_filters.py +159 -0
- katalyst_taxonomy_cli-2.0.1a1/src/katalyst_taxonomy_cli/commands/config_home.py +179 -0
- katalyst_taxonomy_cli-2.0.1a1/src/katalyst_taxonomy_cli/commands/config_plugins.py +180 -0
- katalyst_taxonomy_cli-2.0.1a1/src/katalyst_taxonomy_cli/commands/config_profile.py +604 -0
- katalyst_taxonomy_cli-2.0.1a1/src/katalyst_taxonomy_cli/commands/config_schemas.py +257 -0
- katalyst_taxonomy_cli-2.0.1a1/src/katalyst_taxonomy_cli/commands/config_sources.py +141 -0
- katalyst_taxonomy_cli-2.0.1a1/src/katalyst_taxonomy_cli/commands/core.py +468 -0
- katalyst_taxonomy_cli-2.0.1a1/src/katalyst_taxonomy_cli/commands/create.py +145 -0
- katalyst_taxonomy_cli-2.0.1a1/src/katalyst_taxonomy_cli/commands/deps.py +48 -0
- katalyst_taxonomy_cli-2.0.1a1/src/katalyst_taxonomy_cli/commands/diff.py +63 -0
- katalyst_taxonomy_cli-2.0.1a1/src/katalyst_taxonomy_cli/commands/drafts.py +63 -0
- katalyst_taxonomy_cli-2.0.1a1/src/katalyst_taxonomy_cli/commands/export.py +186 -0
- katalyst_taxonomy_cli-2.0.1a1/src/katalyst_taxonomy_cli/commands/init.py +84 -0
- katalyst_taxonomy_cli-2.0.1a1/src/katalyst_taxonomy_cli/commands/kinds.py +302 -0
- katalyst_taxonomy_cli-2.0.1a1/src/katalyst_taxonomy_cli/commands/lifecycle.py +127 -0
- katalyst_taxonomy_cli-2.0.1a1/src/katalyst_taxonomy_cli/commands/lint.py +256 -0
- katalyst_taxonomy_cli-2.0.1a1/src/katalyst_taxonomy_cli/commands/plugin.py +148 -0
- katalyst_taxonomy_cli-2.0.1a1/src/katalyst_taxonomy_cli/commands/promote.py +95 -0
- katalyst_taxonomy_cli-2.0.1a1/src/katalyst_taxonomy_cli/commands/query.py +187 -0
- katalyst_taxonomy_cli-2.0.1a1/src/katalyst_taxonomy_cli/commands/relate.py +137 -0
- katalyst_taxonomy_cli-2.0.1a1/src/katalyst_taxonomy_cli/commands/relocate.py +129 -0
- katalyst_taxonomy_cli-2.0.1a1/src/katalyst_taxonomy_cli/commands/remove.py +95 -0
- katalyst_taxonomy_cli-2.0.1a1/src/katalyst_taxonomy_cli/commands/resolve.py +48 -0
- katalyst_taxonomy_cli-2.0.1a1/src/katalyst_taxonomy_cli/commands/rollback.py +51 -0
- katalyst_taxonomy_cli-2.0.1a1/src/katalyst_taxonomy_cli/commands/self_update.py +530 -0
- katalyst_taxonomy_cli-2.0.1a1/src/katalyst_taxonomy_cli/commands/show.py +33 -0
- katalyst_taxonomy_cli-2.0.1a1/src/katalyst_taxonomy_cli/commands/status.py +107 -0
- katalyst_taxonomy_cli-2.0.1a1/src/katalyst_taxonomy_cli/commands/sync.py +60 -0
- katalyst_taxonomy_cli-2.0.1a1/src/katalyst_taxonomy_cli/commands/update.py +112 -0
- katalyst_taxonomy_cli-2.0.1a1/src/katalyst_taxonomy_cli/commands/versions.py +54 -0
- katalyst_taxonomy_cli-2.0.1a1/src/katalyst_taxonomy_cli/context.py +145 -0
- katalyst_taxonomy_cli-2.0.1a1/src/katalyst_taxonomy_cli/discovery.py +157 -0
- katalyst_taxonomy_cli-2.0.1a1/src/katalyst_taxonomy_cli/fields.py +70 -0
- katalyst_taxonomy_cli-2.0.1a1/src/katalyst_taxonomy_cli/install.py +330 -0
- katalyst_taxonomy_cli-2.0.1a1/src/katalyst_taxonomy_cli/options.py +20 -0
- katalyst_taxonomy_cli-2.0.1a1/src/katalyst_taxonomy_cli/output.py +92 -0
- katalyst_taxonomy_cli-2.0.1a1/src/katalyst_taxonomy_cli/py.typed +0 -0
- katalyst_taxonomy_cli-2.0.1a1/tests/conftest.py +65 -0
- katalyst_taxonomy_cli-2.0.1a1/tests/test_add.py +251 -0
- katalyst_taxonomy_cli-2.0.1a1/tests/test_add_bundle.py +144 -0
- katalyst_taxonomy_cli-2.0.1a1/tests/test_api_command.py +48 -0
- katalyst_taxonomy_cli-2.0.1a1/tests/test_cli_version.py +30 -0
- katalyst_taxonomy_cli-2.0.1a1/tests/test_clone.py +219 -0
- katalyst_taxonomy_cli-2.0.1a1/tests/test_command_signatures.py +153 -0
- katalyst_taxonomy_cli-2.0.1a1/tests/test_config_command.py +445 -0
- katalyst_taxonomy_cli-2.0.1a1/tests/test_config_explain.py +476 -0
- katalyst_taxonomy_cli-2.0.1a1/tests/test_config_home.py +133 -0
- katalyst_taxonomy_cli-2.0.1a1/tests/test_config_plugins_command.py +399 -0
- katalyst_taxonomy_cli-2.0.1a1/tests/test_config_profiles.py +480 -0
- katalyst_taxonomy_cli-2.0.1a1/tests/test_config_schemas_command.py +305 -0
- katalyst_taxonomy_cli-2.0.1a1/tests/test_config_sources.py +157 -0
- katalyst_taxonomy_cli-2.0.1a1/tests/test_core_schema.py +95 -0
- katalyst_taxonomy_cli-2.0.1a1/tests/test_create.py +582 -0
- katalyst_taxonomy_cli-2.0.1a1/tests/test_create_typed_and_variants.py +203 -0
- katalyst_taxonomy_cli-2.0.1a1/tests/test_deps.py +181 -0
- katalyst_taxonomy_cli-2.0.1a1/tests/test_git_backend_cli.py +118 -0
- katalyst_taxonomy_cli-2.0.1a1/tests/test_github_backend_cli.py +140 -0
- katalyst_taxonomy_cli-2.0.1a1/tests/test_install.py +292 -0
- katalyst_taxonomy_cli-2.0.1a1/tests/test_kinds.py +98 -0
- katalyst_taxonomy_cli-2.0.1a1/tests/test_kinds_plugin_settings.py +70 -0
- katalyst_taxonomy_cli-2.0.1a1/tests/test_lint_output.py +85 -0
- katalyst_taxonomy_cli-2.0.1a1/tests/test_lint_strict.py +127 -0
- katalyst_taxonomy_cli-2.0.1a1/tests/test_output.py +105 -0
- katalyst_taxonomy_cli-2.0.1a1/tests/test_plugin_command.py +113 -0
- katalyst_taxonomy_cli-2.0.1a1/tests/test_plugin_gating.py +204 -0
- katalyst_taxonomy_cli-2.0.1a1/tests/test_relate_update.py +254 -0
- katalyst_taxonomy_cli-2.0.1a1/tests/test_relocate.py +162 -0
- katalyst_taxonomy_cli-2.0.1a1/tests/test_remove.py +137 -0
- katalyst_taxonomy_cli-2.0.1a1/tests/test_repository_targets.py +202 -0
- katalyst_taxonomy_cli-2.0.1a1/tests/test_self_update.py +452 -0
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
# Byte-compiled / optimized / DLL files
|
|
2
|
+
__pycache__/
|
|
3
|
+
*.py[cod]
|
|
4
|
+
*$py.class
|
|
5
|
+
|
|
6
|
+
# C extensions
|
|
7
|
+
*.so
|
|
8
|
+
|
|
9
|
+
# Distribution / packaging
|
|
10
|
+
.Python
|
|
11
|
+
build/
|
|
12
|
+
develop-eggs/
|
|
13
|
+
dist/
|
|
14
|
+
downloads/
|
|
15
|
+
eggs/
|
|
16
|
+
.eggs/
|
|
17
|
+
lib/
|
|
18
|
+
# Not a Python build directory: Bard's SPA keeps shared helpers (utils.ts, download.ts) in
|
|
19
|
+
# src/lib, and ignoring it left a fresh clone unable to build or test the SPA.
|
|
20
|
+
!plugins/bard/web/src/lib/
|
|
21
|
+
lib64/
|
|
22
|
+
parts/
|
|
23
|
+
sdist/
|
|
24
|
+
var/
|
|
25
|
+
wheels/
|
|
26
|
+
*.egg-info/
|
|
27
|
+
.installed.cfg
|
|
28
|
+
*.egg
|
|
29
|
+
|
|
30
|
+
# PyInstaller
|
|
31
|
+
*.manifest
|
|
32
|
+
*.spec
|
|
33
|
+
|
|
34
|
+
# Installer logs
|
|
35
|
+
pip-log.txt
|
|
36
|
+
pip-delete-this-directory.txt
|
|
37
|
+
|
|
38
|
+
# Unit test / coverage reports
|
|
39
|
+
htmlcov/
|
|
40
|
+
.tox/
|
|
41
|
+
.nox/
|
|
42
|
+
.coverage
|
|
43
|
+
.coverage.*
|
|
44
|
+
.cache
|
|
45
|
+
nosetests.xml
|
|
46
|
+
coverage.xml
|
|
47
|
+
*.cover
|
|
48
|
+
*.py,cover
|
|
49
|
+
.hypothesis/
|
|
50
|
+
.pytest_cache/
|
|
51
|
+
|
|
52
|
+
# Translations
|
|
53
|
+
*.mo
|
|
54
|
+
*.pot
|
|
55
|
+
|
|
56
|
+
# Environments
|
|
57
|
+
.env
|
|
58
|
+
.venv
|
|
59
|
+
env/
|
|
60
|
+
venv/
|
|
61
|
+
ENV/
|
|
62
|
+
env.bak/
|
|
63
|
+
venv.bak/
|
|
64
|
+
|
|
65
|
+
# Spyder project settings
|
|
66
|
+
.spyderproject
|
|
67
|
+
.spyproject
|
|
68
|
+
|
|
69
|
+
# Rope project settings
|
|
70
|
+
.ropeproject
|
|
71
|
+
|
|
72
|
+
# mkdocs documentation
|
|
73
|
+
/site
|
|
74
|
+
|
|
75
|
+
# mypy / pyright
|
|
76
|
+
.mypy_cache/
|
|
77
|
+
.dmypy.json
|
|
78
|
+
dmypy.json
|
|
79
|
+
.pyright/
|
|
80
|
+
|
|
81
|
+
# Ruff
|
|
82
|
+
.ruff_cache/
|
|
83
|
+
|
|
84
|
+
# IDE
|
|
85
|
+
.idea/
|
|
86
|
+
.vscode/
|
|
87
|
+
*.swp
|
|
88
|
+
*.swo
|
|
89
|
+
*~
|
|
90
|
+
|
|
91
|
+
# OS
|
|
92
|
+
.DS_Store
|
|
93
|
+
Thumbs.db
|
|
94
|
+
|
|
95
|
+
# Local configuration
|
|
96
|
+
*.local
|
|
97
|
+
|
|
98
|
+
# Browser/Playwright state files
|
|
99
|
+
browser-state-*.json
|
|
100
|
+
|
|
101
|
+
# Agent harness scratch (run exhaust, never the design bundle)
|
|
102
|
+
.opencode/sessions/
|
|
103
|
+
.opencode/analysis/
|
|
104
|
+
.opencode/package-lock.json
|
|
105
|
+
|
|
106
|
+
# Build artifacts
|
|
107
|
+
site/
|
|
108
|
+
stack-tests/cucumber-report/
|
|
109
|
+
stack-tests/test-results/
|
|
110
|
+
|
|
111
|
+
# Generated artifacts (rebuild with `just generate-schemas`)
|
|
112
|
+
taxonomy/src/katalyst_taxonomy/engine/generated/
|
|
113
|
+
.worktrees/
|
|
114
|
+
.plan-74-75-76-77.md
|
|
115
|
+
.HANDOFF-scaffolding-wiring.md
|
|
116
|
+
.HANDOFF-2-cicd-migration.md
|
|
117
|
+
.HANDOFF-action-execution-fixes.md
|
|
118
|
+
.HANDOFF-bdd-coverage.md
|
|
119
|
+
.HANDOFF-*.md
|
|
120
|
+
docs/superpowers/
|
|
121
|
+
|
|
122
|
+
# Node dependencies — installed, never committed
|
|
123
|
+
node_modules/
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: katalyst-taxonomy-cli
|
|
3
|
+
Version: 2.0.1a1
|
|
4
|
+
Summary: CLI for katalyst-taxonomy — the kata command
|
|
5
|
+
Author: eSimplicity
|
|
6
|
+
License: Proprietary
|
|
7
|
+
Requires-Python: >=3.12
|
|
8
|
+
Requires-Dist: click>=8.1
|
|
9
|
+
Requires-Dist: katalyst-core<1,>=0.1
|
|
10
|
+
Requires-Dist: katalyst-engine<6,>=5
|
|
11
|
+
Requires-Dist: katalyst-taxonomy-mcp<5,>=4
|
|
12
|
+
Requires-Dist: katalyst-taxonomy<6,>=5
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "katalyst-taxonomy-cli"
|
|
3
|
+
version = "2.0.1a1"
|
|
4
|
+
description = "CLI for katalyst-taxonomy — the kata command"
|
|
5
|
+
license = { text = "Proprietary" }
|
|
6
|
+
requires-python = ">=3.12"
|
|
7
|
+
authors = [
|
|
8
|
+
{ name = "eSimplicity" }
|
|
9
|
+
]
|
|
10
|
+
dependencies = [
|
|
11
|
+
# Bounded: unbounded, this resolved against the published katalyst-taxonomy 3.x and
|
|
12
|
+
# katalyst-taxonomy-mcp 3.2.0, which predate the config-rooted runtime the CLI is.
|
|
13
|
+
"katalyst-taxonomy>=5,<6",
|
|
14
|
+
"katalyst-taxonomy-mcp>=4,<5",
|
|
15
|
+
"katalyst-engine>=5,<6",
|
|
16
|
+
"click>=8.1",
|
|
17
|
+
"katalyst-core>=0.1,<1",
|
|
18
|
+
]
|
|
19
|
+
|
|
20
|
+
[project.scripts]
|
|
21
|
+
kata = "katalyst_taxonomy_cli.__main__:main"
|
|
22
|
+
|
|
23
|
+
[build-system]
|
|
24
|
+
requires = ["hatchling"]
|
|
25
|
+
build-backend = "hatchling.build"
|
|
26
|
+
|
|
27
|
+
[tool.hatch.build.targets.wheel]
|
|
28
|
+
packages = ["src/katalyst_taxonomy_cli"]
|
|
29
|
+
|
|
30
|
+
[tool.hatch.build.targets.wheel.sources]
|
|
31
|
+
"src" = ""
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
"""Katalyst CLI package."""
|
|
2
|
+
|
|
3
|
+
from importlib.metadata import PackageNotFoundError
|
|
4
|
+
from importlib.metadata import version as _dist_version
|
|
5
|
+
|
|
6
|
+
#: This package's own distribution: the one ``kata`` runs from.
|
|
7
|
+
DISTRIBUTION = "katalyst-taxonomy-cli"
|
|
8
|
+
|
|
9
|
+
try:
|
|
10
|
+
__version__ = _dist_version(DISTRIBUTION)
|
|
11
|
+
except PackageNotFoundError: # pragma: no cover - source tree without an install
|
|
12
|
+
__version__ = "0.0.0+unknown"
|
|
13
|
+
|
|
14
|
+
__all__ = ["DISTRIBUTION", "__version__"]
|
|
@@ -0,0 +1,248 @@
|
|
|
1
|
+
"""Internal wiring — factory functions for service instantiation.
|
|
2
|
+
|
|
3
|
+
This is the CLI's composition root for services that need adapter wiring.
|
|
4
|
+
Commands should never import adapters directly — they get services from here.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
from dataclasses import dataclass
|
|
10
|
+
from pathlib import Path
|
|
11
|
+
from typing import TYPE_CHECKING
|
|
12
|
+
|
|
13
|
+
import click
|
|
14
|
+
|
|
15
|
+
if TYPE_CHECKING: # pragma: no cover - typing only
|
|
16
|
+
from katalyst_taxonomy.config.backends import BackendRegistry
|
|
17
|
+
from katalyst_taxonomy.ports.backend import PromotableBackend, TaxonomyBackend
|
|
18
|
+
|
|
19
|
+
from .context import CliContext
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def create_bundle_service(): # noqa: ANN201
|
|
23
|
+
"""Wire up BundleService with the concrete bundle sources."""
|
|
24
|
+
from katalyst_taxonomy.services.bundle_service import BundleService
|
|
25
|
+
from katalyst_taxonomy.sources.bundle_sources import default_bundle_sources
|
|
26
|
+
|
|
27
|
+
return BundleService(sources=list(default_bundle_sources()))
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def create_plugin_install_service(): # noqa: ANN201
|
|
31
|
+
"""Wire up PluginInstallService, recording in the user config file."""
|
|
32
|
+
from katalyst_taxonomy.services.plugin_install import plugin_install_service
|
|
33
|
+
|
|
34
|
+
return plugin_install_service()
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def create_sync_service(name: str, root: Path, *, profile: str | None = None): # noqa: ANN201
|
|
38
|
+
"""Wire up SyncService with concrete adapters.
|
|
39
|
+
|
|
40
|
+
The source is one repository — the filesystem backend *name*, rooted at *root* —
|
|
41
|
+
because sync's job is "push what is on disk". It comes from the registry, so it is
|
|
42
|
+
named (the nodes it syncs carry their real backend) and subject to the active
|
|
43
|
+
profile's ``r`` bit (#216). Targets come from the registry and are gated on ``w``
|
|
44
|
+
by construction.
|
|
45
|
+
"""
|
|
46
|
+
from katalyst_taxonomy.adapters.databricks.sync import DatabricksAppender
|
|
47
|
+
from katalyst_taxonomy.config.backends import load_backends
|
|
48
|
+
from katalyst_taxonomy.services.sync import SyncService
|
|
49
|
+
|
|
50
|
+
registry = load_backends(root, profile=profile)
|
|
51
|
+
return SyncService(
|
|
52
|
+
source=registry.get(name),
|
|
53
|
+
registry=registry,
|
|
54
|
+
base_path=root,
|
|
55
|
+
appenders=(DatabricksAppender(),),
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
# ---------------------------------------------------------------------------
|
|
60
|
+
# Backend resolution
|
|
61
|
+
# ---------------------------------------------------------------------------
|
|
62
|
+
@dataclass(frozen=True, slots=True)
|
|
63
|
+
class ResolvedBackend:
|
|
64
|
+
"""The one backend a command operates on.
|
|
65
|
+
|
|
66
|
+
Attributes:
|
|
67
|
+
registry: The registry, so a command can still address a second backend.
|
|
68
|
+
name: The backend the command named.
|
|
69
|
+
backend: The constructed backend.
|
|
70
|
+
fqtn: The address with its ``backend:`` prefix and ``#kind`` selector stripped, or
|
|
71
|
+
``""`` when the command supplied no address. This is what a command passes
|
|
72
|
+
downstream.
|
|
73
|
+
selector: The ``#kind`` narrowing, or ``None``.
|
|
74
|
+
"""
|
|
75
|
+
|
|
76
|
+
registry: BackendRegistry
|
|
77
|
+
name: str
|
|
78
|
+
backend: TaxonomyBackend
|
|
79
|
+
fqtn: str = ""
|
|
80
|
+
selector: str | None = None
|
|
81
|
+
|
|
82
|
+
@property
|
|
83
|
+
def address(self) -> str:
|
|
84
|
+
"""The FQTN with its ``#kind`` selector, as the services resolve it."""
|
|
85
|
+
return f"{self.fqtn}#{self.selector}" if self.selector else self.fqtn
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
@dataclass(frozen=True, slots=True)
|
|
89
|
+
class ResolvedRead:
|
|
90
|
+
"""The backends a listing reads, and the address it was narrowed to.
|
|
91
|
+
|
|
92
|
+
Attributes:
|
|
93
|
+
registry: The registry.
|
|
94
|
+
sources: ``(name, backend)`` pairs — one when the command named a backend, otherwise
|
|
95
|
+
every backend the active profile lists.
|
|
96
|
+
fqtn: The address's FQTN with its prefix and selector stripped, or ``""``.
|
|
97
|
+
selector: The ``#kind`` narrowing, or ``None``.
|
|
98
|
+
"""
|
|
99
|
+
|
|
100
|
+
registry: BackendRegistry
|
|
101
|
+
sources: tuple[tuple[str, TaxonomyBackend], ...]
|
|
102
|
+
fqtn: str = ""
|
|
103
|
+
selector: str | None = None
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
def load_registry(ctx: CliContext) -> BackendRegistry:
|
|
107
|
+
"""Load the backend registry for *ctx*, honoring the active profile."""
|
|
108
|
+
from katalyst_taxonomy.config.backends import load_backends
|
|
109
|
+
|
|
110
|
+
return load_backends(ctx.cwd, profile=ctx.profile)
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
def _registry(ctx: CliContext) -> BackendRegistry:
|
|
114
|
+
try:
|
|
115
|
+
return load_registry(ctx)
|
|
116
|
+
except Exception as exc: # noqa: BLE001 - config errors are user errors, not tracebacks
|
|
117
|
+
raise click.ClickException(str(exc)) from exc
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
def _named(registry: BackendRegistry, target: str | None, address: str) -> tuple[str | None, str]:
|
|
121
|
+
"""The backend named by *address* or *target*, and the bare FQTN.
|
|
122
|
+
|
|
123
|
+
A prefix and ``--backend`` that disagree is an error rather than one silently
|
|
124
|
+
winning: the command was told two different stores.
|
|
125
|
+
"""
|
|
126
|
+
if address:
|
|
127
|
+
parsed = registry.address(address)
|
|
128
|
+
if target and target != parsed.backend:
|
|
129
|
+
raise click.ClickException(
|
|
130
|
+
f"{address!r} names backend {parsed.backend!r} but --backend is {target!r}."
|
|
131
|
+
)
|
|
132
|
+
return parsed.backend, parsed.fqtn
|
|
133
|
+
if target:
|
|
134
|
+
if target not in registry.profile_backends():
|
|
135
|
+
from katalyst_taxonomy.config.routing import UnknownBackendError
|
|
136
|
+
|
|
137
|
+
raise UnknownBackendError(target, registry.profile_backends())
|
|
138
|
+
return target, ""
|
|
139
|
+
return None, ""
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
def resolve_backend(
|
|
143
|
+
ctx: CliContext,
|
|
144
|
+
target: str | None = None,
|
|
145
|
+
*,
|
|
146
|
+
address: str | None = None,
|
|
147
|
+
) -> ResolvedBackend:
|
|
148
|
+
"""The single backend a command writes to or operates on. It must be named.
|
|
149
|
+
|
|
150
|
+
Named by the address's ``backend:`` prefix, or by ``--backend``. Neither is an error
|
|
151
|
+
listing the profile's backends: there is no default, so nothing is chosen for the
|
|
152
|
+
caller.
|
|
153
|
+
|
|
154
|
+
Raises:
|
|
155
|
+
click.ClickException: If the configuration is invalid, nothing names a backend, the
|
|
156
|
+
address is unprefixed, or the backend named is not one the profile reads.
|
|
157
|
+
"""
|
|
158
|
+
from katalyst_taxonomy.config.routing import BackendAddressError
|
|
159
|
+
from katalyst_taxonomy.engine.qualification import split_kind_selector
|
|
160
|
+
|
|
161
|
+
registry = _registry(ctx)
|
|
162
|
+
routable, selector = split_kind_selector(address or "")
|
|
163
|
+
try:
|
|
164
|
+
name, fqtn = _named(registry, target, routable)
|
|
165
|
+
if name is None:
|
|
166
|
+
reads = ", ".join(registry.profile_backends())
|
|
167
|
+
raise click.ClickException(
|
|
168
|
+
f"Name the backend to use with --backend. This profile reads: {reads}."
|
|
169
|
+
)
|
|
170
|
+
backend = registry.get(name)
|
|
171
|
+
except BackendAddressError as exc:
|
|
172
|
+
raise click.ClickException(str(exc)) from exc
|
|
173
|
+
return ResolvedBackend(
|
|
174
|
+
registry=registry, name=name, backend=backend, fqtn=fqtn, selector=selector
|
|
175
|
+
)
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
def resolve_read(
|
|
179
|
+
ctx: CliContext,
|
|
180
|
+
target: str | None = None,
|
|
181
|
+
*,
|
|
182
|
+
address: str | None = None,
|
|
183
|
+
) -> ResolvedRead:
|
|
184
|
+
"""The backends a listing reads: the one named, or all of the profile's.
|
|
185
|
+
|
|
186
|
+
An address still has to carry a prefix — an unprefixed name is an error, not a search
|
|
187
|
+
across backends.
|
|
188
|
+
|
|
189
|
+
Raises:
|
|
190
|
+
click.ClickException: If the configuration is invalid, the address is unprefixed,
|
|
191
|
+
or a named backend is unknown or unavailable.
|
|
192
|
+
"""
|
|
193
|
+
from katalyst_taxonomy.config.routing import BackendAddressError
|
|
194
|
+
from katalyst_taxonomy.engine.qualification import split_kind_selector
|
|
195
|
+
|
|
196
|
+
registry = _registry(ctx)
|
|
197
|
+
routable, selector = split_kind_selector(address or "")
|
|
198
|
+
try:
|
|
199
|
+
name, fqtn = _named(registry, target, routable)
|
|
200
|
+
sources = registry.read_set(name)
|
|
201
|
+
except BackendAddressError as exc:
|
|
202
|
+
raise click.ClickException(str(exc)) from exc
|
|
203
|
+
return ResolvedRead(registry=registry, sources=sources, fqtn=fqtn, selector=selector)
|
|
204
|
+
|
|
205
|
+
|
|
206
|
+
def resolve_promotable_backend(
|
|
207
|
+
ctx: CliContext,
|
|
208
|
+
target: str | None = None,
|
|
209
|
+
*,
|
|
210
|
+
address: str | None = None,
|
|
211
|
+
capability: str = "promotion workflows",
|
|
212
|
+
operation: str | None = None,
|
|
213
|
+
) -> tuple[str, PromotableBackend, str]:
|
|
214
|
+
"""Resolve a backend that must satisfy :class:`PromotableBackend`.
|
|
215
|
+
|
|
216
|
+
A separate function rather than a ``requires=`` parameter on
|
|
217
|
+
:func:`resolve_backend` so the return type is *narrowed* — the five promotion
|
|
218
|
+
commands then call ``backend.promote_node`` and friends with no ``isinstance``
|
|
219
|
+
dance and no loss of type information, which a generic ``type[Any]`` guard
|
|
220
|
+
would have thrown away.
|
|
221
|
+
|
|
222
|
+
Args:
|
|
223
|
+
ctx: The CLI context.
|
|
224
|
+
target: Explicit ``--backend`` name.
|
|
225
|
+
address: A ``backend:fqtn`` the command operates on. Its prefix names the backend.
|
|
226
|
+
capability: Operation name used in the error message.
|
|
227
|
+
operation: When the command *changes* the backend (``promote``, ``rollback``),
|
|
228
|
+
its name — a read-only backend is then refused with ``ReadOnlyBackendError``
|
|
229
|
+
rather than "does not support promotion workflows", which is true but not
|
|
230
|
+
the reason.
|
|
231
|
+
|
|
232
|
+
Returns:
|
|
233
|
+
The resolved name, the narrowed backend, and the bare FQTN (``""`` without one).
|
|
234
|
+
|
|
235
|
+
Raises:
|
|
236
|
+
SystemExit: With status 1 if the backend does not support *capability*.
|
|
237
|
+
"""
|
|
238
|
+
from katalyst_taxonomy.ports.backend import PromotableBackend as _Promotable
|
|
239
|
+
|
|
240
|
+
resolved = resolve_backend(ctx, target, address=address)
|
|
241
|
+
if operation is not None:
|
|
242
|
+
from katalyst_taxonomy.ports.backend import require_writable
|
|
243
|
+
|
|
244
|
+
require_writable(resolved.backend, operation, target=resolved.fqtn)
|
|
245
|
+
if not isinstance(resolved.backend, _Promotable):
|
|
246
|
+
ctx.output.echo(f"Error: Backend '{resolved.name}' does not support {capability}.")
|
|
247
|
+
raise SystemExit(1)
|
|
248
|
+
return resolved.name, resolved.backend, resolved.fqtn
|