scc-cli 1.5.3__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.
Potentially problematic release.
This version of scc-cli might be problematic. Click here for more details.
- scc_cli/__init__.py +15 -0
- scc_cli/audit/__init__.py +37 -0
- scc_cli/audit/parser.py +191 -0
- scc_cli/audit/reader.py +180 -0
- scc_cli/auth.py +145 -0
- scc_cli/claude_adapter.py +485 -0
- scc_cli/cli.py +311 -0
- scc_cli/cli_common.py +190 -0
- scc_cli/cli_helpers.py +244 -0
- scc_cli/commands/__init__.py +20 -0
- scc_cli/commands/admin.py +708 -0
- scc_cli/commands/audit.py +246 -0
- scc_cli/commands/config.py +528 -0
- scc_cli/commands/exceptions.py +696 -0
- scc_cli/commands/init.py +272 -0
- scc_cli/commands/launch/__init__.py +73 -0
- scc_cli/commands/launch/app.py +1247 -0
- scc_cli/commands/launch/render.py +309 -0
- scc_cli/commands/launch/sandbox.py +135 -0
- scc_cli/commands/launch/workspace.py +339 -0
- scc_cli/commands/org/__init__.py +49 -0
- scc_cli/commands/org/_builders.py +264 -0
- scc_cli/commands/org/app.py +41 -0
- scc_cli/commands/org/import_cmd.py +267 -0
- scc_cli/commands/org/init_cmd.py +269 -0
- scc_cli/commands/org/schema_cmd.py +76 -0
- scc_cli/commands/org/status_cmd.py +157 -0
- scc_cli/commands/org/update_cmd.py +330 -0
- scc_cli/commands/org/validate_cmd.py +138 -0
- scc_cli/commands/support.py +323 -0
- scc_cli/commands/team.py +910 -0
- scc_cli/commands/worktree/__init__.py +72 -0
- scc_cli/commands/worktree/_helpers.py +57 -0
- scc_cli/commands/worktree/app.py +170 -0
- scc_cli/commands/worktree/container_commands.py +385 -0
- scc_cli/commands/worktree/context_commands.py +61 -0
- scc_cli/commands/worktree/session_commands.py +128 -0
- scc_cli/commands/worktree/worktree_commands.py +734 -0
- scc_cli/config.py +647 -0
- scc_cli/confirm.py +20 -0
- scc_cli/console.py +562 -0
- scc_cli/contexts.py +394 -0
- scc_cli/core/__init__.py +68 -0
- scc_cli/core/constants.py +101 -0
- scc_cli/core/errors.py +297 -0
- scc_cli/core/exit_codes.py +91 -0
- scc_cli/core/workspace.py +57 -0
- scc_cli/deprecation.py +54 -0
- scc_cli/deps.py +189 -0
- scc_cli/docker/__init__.py +127 -0
- scc_cli/docker/core.py +467 -0
- scc_cli/docker/credentials.py +726 -0
- scc_cli/docker/launch.py +595 -0
- scc_cli/doctor/__init__.py +105 -0
- scc_cli/doctor/checks/__init__.py +166 -0
- scc_cli/doctor/checks/cache.py +314 -0
- scc_cli/doctor/checks/config.py +107 -0
- scc_cli/doctor/checks/environment.py +182 -0
- scc_cli/doctor/checks/json_helpers.py +157 -0
- scc_cli/doctor/checks/organization.py +264 -0
- scc_cli/doctor/checks/worktree.py +278 -0
- scc_cli/doctor/render.py +365 -0
- scc_cli/doctor/types.py +66 -0
- scc_cli/evaluation/__init__.py +27 -0
- scc_cli/evaluation/apply_exceptions.py +207 -0
- scc_cli/evaluation/evaluate.py +97 -0
- scc_cli/evaluation/models.py +80 -0
- scc_cli/git.py +84 -0
- scc_cli/json_command.py +166 -0
- scc_cli/json_output.py +159 -0
- scc_cli/kinds.py +65 -0
- scc_cli/marketplace/__init__.py +123 -0
- scc_cli/marketplace/adapter.py +74 -0
- scc_cli/marketplace/compute.py +377 -0
- scc_cli/marketplace/constants.py +87 -0
- scc_cli/marketplace/managed.py +135 -0
- scc_cli/marketplace/materialize.py +846 -0
- scc_cli/marketplace/normalize.py +548 -0
- scc_cli/marketplace/render.py +281 -0
- scc_cli/marketplace/resolve.py +459 -0
- scc_cli/marketplace/schema.py +506 -0
- scc_cli/marketplace/sync.py +279 -0
- scc_cli/marketplace/team_cache.py +195 -0
- scc_cli/marketplace/team_fetch.py +689 -0
- scc_cli/marketplace/trust.py +244 -0
- scc_cli/models/__init__.py +41 -0
- scc_cli/models/exceptions.py +273 -0
- scc_cli/models/plugin_audit.py +434 -0
- scc_cli/org_templates.py +269 -0
- scc_cli/output_mode.py +167 -0
- scc_cli/panels.py +113 -0
- scc_cli/platform.py +350 -0
- scc_cli/profiles.py +960 -0
- scc_cli/remote.py +443 -0
- scc_cli/schemas/__init__.py +1 -0
- scc_cli/schemas/org-v1.schema.json +456 -0
- scc_cli/schemas/team-config.v1.schema.json +163 -0
- scc_cli/services/__init__.py +1 -0
- scc_cli/services/git/__init__.py +79 -0
- scc_cli/services/git/branch.py +151 -0
- scc_cli/services/git/core.py +216 -0
- scc_cli/services/git/hooks.py +108 -0
- scc_cli/services/git/worktree.py +444 -0
- scc_cli/services/workspace/__init__.py +36 -0
- scc_cli/services/workspace/resolver.py +223 -0
- scc_cli/services/workspace/suspicious.py +200 -0
- scc_cli/sessions.py +425 -0
- scc_cli/setup.py +589 -0
- scc_cli/source_resolver.py +470 -0
- scc_cli/stats.py +378 -0
- scc_cli/stores/__init__.py +13 -0
- scc_cli/stores/exception_store.py +251 -0
- scc_cli/subprocess_utils.py +88 -0
- scc_cli/teams.py +383 -0
- scc_cli/templates/__init__.py +2 -0
- scc_cli/templates/org/__init__.py +0 -0
- scc_cli/templates/org/minimal.json +19 -0
- scc_cli/templates/org/reference.json +74 -0
- scc_cli/templates/org/strict.json +38 -0
- scc_cli/templates/org/teams.json +42 -0
- scc_cli/templates/statusline.sh +75 -0
- scc_cli/theme.py +348 -0
- scc_cli/ui/__init__.py +154 -0
- scc_cli/ui/branding.py +68 -0
- scc_cli/ui/chrome.py +401 -0
- scc_cli/ui/dashboard/__init__.py +62 -0
- scc_cli/ui/dashboard/_dashboard.py +794 -0
- scc_cli/ui/dashboard/loaders.py +452 -0
- scc_cli/ui/dashboard/models.py +185 -0
- scc_cli/ui/dashboard/orchestrator.py +735 -0
- scc_cli/ui/formatters.py +444 -0
- scc_cli/ui/gate.py +350 -0
- scc_cli/ui/git_interactive.py +869 -0
- scc_cli/ui/git_render.py +176 -0
- scc_cli/ui/help.py +157 -0
- scc_cli/ui/keys.py +615 -0
- scc_cli/ui/list_screen.py +437 -0
- scc_cli/ui/picker.py +763 -0
- scc_cli/ui/prompts.py +201 -0
- scc_cli/ui/quick_resume.py +116 -0
- scc_cli/ui/wizard.py +576 -0
- scc_cli/update.py +680 -0
- scc_cli/utils/__init__.py +39 -0
- scc_cli/utils/fixit.py +264 -0
- scc_cli/utils/fuzzy.py +124 -0
- scc_cli/utils/locks.py +114 -0
- scc_cli/utils/ttl.py +376 -0
- scc_cli/validate.py +455 -0
- scc_cli-1.5.3.dist-info/METADATA +401 -0
- scc_cli-1.5.3.dist-info/RECORD +153 -0
- scc_cli-1.5.3.dist-info/WHEEL +4 -0
- scc_cli-1.5.3.dist-info/entry_points.txt +2 -0
- scc_cli-1.5.3.dist-info/licenses/LICENSE +21 -0
scc_cli/json_output.py
ADDED
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
"""
|
|
2
|
+
JSON envelope builder for CLI output.
|
|
3
|
+
|
|
4
|
+
Provide structured, versioned JSON envelopes for machine-readable output.
|
|
5
|
+
All JSON output MUST use this builder to ensure consistency.
|
|
6
|
+
|
|
7
|
+
Usage:
|
|
8
|
+
from scc_cli.json_output import build_envelope
|
|
9
|
+
from scc_cli.kinds import Kind
|
|
10
|
+
|
|
11
|
+
envelope = build_envelope(Kind.TEAM_LIST, data={"teams": [...]})
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
from datetime import datetime, timezone
|
|
15
|
+
from typing import Any
|
|
16
|
+
|
|
17
|
+
from . import __version__
|
|
18
|
+
from .core.errors import SCCError
|
|
19
|
+
from .kinds import Kind
|
|
20
|
+
|
|
21
|
+
# ═══════════════════════════════════════════════════════════════════════════════
|
|
22
|
+
# Constants
|
|
23
|
+
# ═══════════════════════════════════════════════════════════════════════════════
|
|
24
|
+
|
|
25
|
+
API_VERSION = "scc.cli/v1"
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
# ═══════════════════════════════════════════════════════════════════════════════
|
|
29
|
+
# Envelope Builder
|
|
30
|
+
# ═══════════════════════════════════════════════════════════════════════════════
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def build_envelope(
|
|
34
|
+
kind: Kind,
|
|
35
|
+
*,
|
|
36
|
+
data: dict[str, Any] | None = None,
|
|
37
|
+
ok: bool = True,
|
|
38
|
+
errors: list[str] | None = None,
|
|
39
|
+
warnings: list[str] | None = None,
|
|
40
|
+
) -> dict[str, Any]:
|
|
41
|
+
"""Build a JSON envelope with standard structure.
|
|
42
|
+
|
|
43
|
+
All JSON output follows this contract for consistency and parseability.
|
|
44
|
+
|
|
45
|
+
Args:
|
|
46
|
+
kind: The envelope kind (from Kind enum)
|
|
47
|
+
data: The command-specific payload
|
|
48
|
+
ok: Whether the operation was successful
|
|
49
|
+
errors: List of error messages (sets ok=False if non-empty)
|
|
50
|
+
warnings: List of warning messages
|
|
51
|
+
|
|
52
|
+
Returns:
|
|
53
|
+
A structured envelope dict ready for JSON serialization:
|
|
54
|
+
{
|
|
55
|
+
"apiVersion": "scc.cli/v1",
|
|
56
|
+
"kind": "TeamList",
|
|
57
|
+
"metadata": {
|
|
58
|
+
"generatedAt": "2025-12-23T10:00:00Z",
|
|
59
|
+
"cliVersion": "1.2.3"
|
|
60
|
+
},
|
|
61
|
+
"status": {
|
|
62
|
+
"ok": true,
|
|
63
|
+
"errors": [],
|
|
64
|
+
"warnings": []
|
|
65
|
+
},
|
|
66
|
+
"data": { ... }
|
|
67
|
+
}
|
|
68
|
+
"""
|
|
69
|
+
# Normalize optional parameters
|
|
70
|
+
if data is None:
|
|
71
|
+
data = {}
|
|
72
|
+
if errors is None:
|
|
73
|
+
errors = []
|
|
74
|
+
if warnings is None:
|
|
75
|
+
warnings = []
|
|
76
|
+
|
|
77
|
+
# If errors provided, ok should be False
|
|
78
|
+
if errors and ok:
|
|
79
|
+
ok = False
|
|
80
|
+
|
|
81
|
+
# Generate ISO 8601 timestamp in UTC
|
|
82
|
+
generated_at = datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ")
|
|
83
|
+
|
|
84
|
+
return {
|
|
85
|
+
"apiVersion": API_VERSION,
|
|
86
|
+
"kind": str(kind.value) if hasattr(kind, "value") else str(kind),
|
|
87
|
+
"metadata": {
|
|
88
|
+
"generatedAt": generated_at,
|
|
89
|
+
"cliVersion": __version__,
|
|
90
|
+
},
|
|
91
|
+
"status": {
|
|
92
|
+
"ok": ok,
|
|
93
|
+
"errors": errors,
|
|
94
|
+
"warnings": warnings,
|
|
95
|
+
},
|
|
96
|
+
"data": data,
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
def build_error_envelope(exc: Exception) -> dict[str, Any]:
|
|
101
|
+
"""Build a JSON error envelope from an exception.
|
|
102
|
+
|
|
103
|
+
This is the canonical error format for JSON mode output.
|
|
104
|
+
All errors should go through this function to ensure consistency.
|
|
105
|
+
|
|
106
|
+
Args:
|
|
107
|
+
exc: The exception to convert to JSON envelope
|
|
108
|
+
|
|
109
|
+
Returns:
|
|
110
|
+
A structured error envelope dict ready for JSON serialization:
|
|
111
|
+
{
|
|
112
|
+
"apiVersion": "scc.cli/v1",
|
|
113
|
+
"kind": "Error",
|
|
114
|
+
"metadata": { ... },
|
|
115
|
+
"status": {
|
|
116
|
+
"ok": false,
|
|
117
|
+
"errors": ["Error message"],
|
|
118
|
+
"warnings": []
|
|
119
|
+
},
|
|
120
|
+
"data": {
|
|
121
|
+
"error_type": "SCCError",
|
|
122
|
+
"user_message": "...",
|
|
123
|
+
"suggested_action": "...",
|
|
124
|
+
"debug_context": "..." # Only for SCCError
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
"""
|
|
128
|
+
# Generate ISO 8601 timestamp in UTC
|
|
129
|
+
generated_at = datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ")
|
|
130
|
+
|
|
131
|
+
# Build error data depending on exception type
|
|
132
|
+
error_data: dict[str, Any] = {
|
|
133
|
+
"error_type": type(exc).__name__,
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
if isinstance(exc, SCCError):
|
|
137
|
+
error_data["user_message"] = exc.user_message
|
|
138
|
+
if exc.suggested_action:
|
|
139
|
+
error_data["suggested_action"] = exc.suggested_action
|
|
140
|
+
if exc.debug_context:
|
|
141
|
+
error_data["debug_context"] = exc.debug_context
|
|
142
|
+
error_message = exc.user_message
|
|
143
|
+
else:
|
|
144
|
+
error_message = str(exc)
|
|
145
|
+
|
|
146
|
+
return {
|
|
147
|
+
"apiVersion": API_VERSION,
|
|
148
|
+
"kind": str(Kind.ERROR.value),
|
|
149
|
+
"metadata": {
|
|
150
|
+
"generatedAt": generated_at,
|
|
151
|
+
"cliVersion": __version__,
|
|
152
|
+
},
|
|
153
|
+
"status": {
|
|
154
|
+
"ok": False,
|
|
155
|
+
"errors": [error_message],
|
|
156
|
+
"warnings": [],
|
|
157
|
+
},
|
|
158
|
+
"data": error_data,
|
|
159
|
+
}
|
scc_cli/kinds.py
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
"""Define centralized JSON envelope kind names to prevent drift.
|
|
2
|
+
|
|
3
|
+
Define all JSON envelope `kind` values here as enum members.
|
|
4
|
+
This prevents inconsistencies like "TeamList" vs "TeamsList" across the codebase.
|
|
5
|
+
|
|
6
|
+
Usage:
|
|
7
|
+
from scc_cli.kinds import Kind
|
|
8
|
+
|
|
9
|
+
envelope = build_envelope(Kind.TEAM_LIST, data={...})
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
from enum import Enum
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class Kind(str, Enum):
|
|
16
|
+
"""Define JSON envelope kind identifiers.
|
|
17
|
+
|
|
18
|
+
Inherit from str so enum values serialize directly to JSON without .value.
|
|
19
|
+
Add new kinds here to ensure consistency across all commands.
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
# Team commands
|
|
23
|
+
TEAM_LIST = "TeamList"
|
|
24
|
+
TEAM_INFO = "TeamInfo"
|
|
25
|
+
TEAM_CURRENT = "TeamCurrent"
|
|
26
|
+
TEAM_SWITCH = "TeamSwitch"
|
|
27
|
+
TEAM_VALIDATE = "TeamValidate"
|
|
28
|
+
|
|
29
|
+
# Status/Doctor
|
|
30
|
+
STATUS = "Status"
|
|
31
|
+
DOCTOR_REPORT = "DoctorReport"
|
|
32
|
+
|
|
33
|
+
# Worktree commands
|
|
34
|
+
WORKTREE_LIST = "WorktreeList"
|
|
35
|
+
WORKTREE_CREATE = "WorktreeCreate"
|
|
36
|
+
WORKTREE_REMOVE = "WorktreeRemove"
|
|
37
|
+
|
|
38
|
+
# Session/Container
|
|
39
|
+
SESSION_LIST = "SessionList"
|
|
40
|
+
CONTAINER_LIST = "ContainerList"
|
|
41
|
+
|
|
42
|
+
# Org admin
|
|
43
|
+
ORG_VALIDATION = "OrgValidation"
|
|
44
|
+
ORG_SCHEMA = "OrgSchema"
|
|
45
|
+
ORG_STATUS = "OrgStatus"
|
|
46
|
+
ORG_IMPORT = "OrgImport"
|
|
47
|
+
ORG_IMPORT_PREVIEW = "OrgImportPreview"
|
|
48
|
+
ORG_INIT = "OrgInit"
|
|
49
|
+
ORG_TEMPLATE_LIST = "OrgTemplateList"
|
|
50
|
+
ORG_UPDATE = "OrgUpdate"
|
|
51
|
+
|
|
52
|
+
# Support
|
|
53
|
+
SUPPORT_BUNDLE = "SupportBundle"
|
|
54
|
+
|
|
55
|
+
# Config
|
|
56
|
+
CONFIG_EXPLAIN = "ConfigExplain"
|
|
57
|
+
|
|
58
|
+
# Start
|
|
59
|
+
START_DRY_RUN = "StartDryRun"
|
|
60
|
+
|
|
61
|
+
# Init
|
|
62
|
+
INIT_RESULT = "InitResult"
|
|
63
|
+
|
|
64
|
+
# Error (used by handle_errors in JSON mode)
|
|
65
|
+
ERROR = "Error"
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Marketplace and plugin management for SCC.
|
|
3
|
+
|
|
4
|
+
This package provides organization-level plugin governance with:
|
|
5
|
+
- Multi-source marketplace definitions (GitHub, Git, URL, directory)
|
|
6
|
+
- Team-based plugin sets with org defaults inheritance
|
|
7
|
+
- Security policies (blocked plugins with audit trail)
|
|
8
|
+
- Project-local materialization for Docker sandbox compatibility
|
|
9
|
+
|
|
10
|
+
Public API:
|
|
11
|
+
- Schema models: OrgConfig, MarketplaceSource, TeamProfile, SecurityConfig
|
|
12
|
+
- Constants: IMPLICIT_MARKETPLACES, EXIT_CODES
|
|
13
|
+
- Normalization: normalize_plugin(), matches_pattern()
|
|
14
|
+
- Computation: compute_effective_plugins(), EffectivePlugins
|
|
15
|
+
- Materialization: materialize_marketplace(), MaterializedMarketplace
|
|
16
|
+
- Settings: render_settings(), merge_settings()
|
|
17
|
+
- State: ManagedState, load_managed_state(), save_managed_state()
|
|
18
|
+
|
|
19
|
+
Example:
|
|
20
|
+
>>> from scc_cli.marketplace import OrgConfig, compute_effective_plugins
|
|
21
|
+
>>> config = OrgConfig.model_validate(json_data)
|
|
22
|
+
>>> effective = compute_effective_plugins(config, team_id="backend")
|
|
23
|
+
>>> print(effective.enabled_plugins)
|
|
24
|
+
"""
|
|
25
|
+
|
|
26
|
+
from scc_cli.marketplace.compute import (
|
|
27
|
+
BlockedPlugin,
|
|
28
|
+
EffectivePlugins,
|
|
29
|
+
TeamNotFoundError,
|
|
30
|
+
compute_effective_plugins,
|
|
31
|
+
)
|
|
32
|
+
from scc_cli.marketplace.constants import (
|
|
33
|
+
EXIT_CODES,
|
|
34
|
+
IMPLICIT_MARKETPLACES,
|
|
35
|
+
MANAGED_STATE_FILE,
|
|
36
|
+
MARKETPLACE_CACHE_DIR,
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
# Managed State
|
|
40
|
+
from scc_cli.marketplace.managed import (
|
|
41
|
+
ManagedState,
|
|
42
|
+
clear_managed_state,
|
|
43
|
+
load_managed_state,
|
|
44
|
+
save_managed_state,
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
# Materialization
|
|
48
|
+
from scc_cli.marketplace.materialize import (
|
|
49
|
+
GitNotAvailableError,
|
|
50
|
+
InvalidMarketplaceError,
|
|
51
|
+
MaterializationError,
|
|
52
|
+
MaterializedMarketplace,
|
|
53
|
+
materialize_marketplace,
|
|
54
|
+
)
|
|
55
|
+
from scc_cli.marketplace.normalize import (
|
|
56
|
+
AmbiguousMarketplaceError,
|
|
57
|
+
InvalidPluginRefError,
|
|
58
|
+
matches_any_pattern,
|
|
59
|
+
matches_pattern,
|
|
60
|
+
normalize_plugin,
|
|
61
|
+
)
|
|
62
|
+
|
|
63
|
+
# Rendering
|
|
64
|
+
from scc_cli.marketplace.render import (
|
|
65
|
+
check_conflicts,
|
|
66
|
+
merge_settings,
|
|
67
|
+
render_settings,
|
|
68
|
+
)
|
|
69
|
+
from scc_cli.marketplace.schema import (
|
|
70
|
+
DefaultsConfig,
|
|
71
|
+
MarketplaceSource,
|
|
72
|
+
MarketplaceSourceDirectory,
|
|
73
|
+
MarketplaceSourceGit,
|
|
74
|
+
MarketplaceSourceGitHub,
|
|
75
|
+
MarketplaceSourceURL,
|
|
76
|
+
OrganizationConfig,
|
|
77
|
+
SecurityConfig,
|
|
78
|
+
TeamProfile,
|
|
79
|
+
)
|
|
80
|
+
|
|
81
|
+
__all__ = [
|
|
82
|
+
# Constants
|
|
83
|
+
"EXIT_CODES",
|
|
84
|
+
"IMPLICIT_MARKETPLACES",
|
|
85
|
+
"MARKETPLACE_CACHE_DIR",
|
|
86
|
+
"MANAGED_STATE_FILE",
|
|
87
|
+
# Schema models
|
|
88
|
+
"OrganizationConfig",
|
|
89
|
+
"MarketplaceSource",
|
|
90
|
+
"MarketplaceSourceGitHub",
|
|
91
|
+
"MarketplaceSourceGit",
|
|
92
|
+
"MarketplaceSourceURL",
|
|
93
|
+
"MarketplaceSourceDirectory",
|
|
94
|
+
"TeamProfile",
|
|
95
|
+
"SecurityConfig",
|
|
96
|
+
"DefaultsConfig",
|
|
97
|
+
# Normalization
|
|
98
|
+
"normalize_plugin",
|
|
99
|
+
"matches_pattern",
|
|
100
|
+
"matches_any_pattern",
|
|
101
|
+
"InvalidPluginRefError",
|
|
102
|
+
"AmbiguousMarketplaceError",
|
|
103
|
+
# Computation
|
|
104
|
+
"compute_effective_plugins",
|
|
105
|
+
"EffectivePlugins",
|
|
106
|
+
"BlockedPlugin",
|
|
107
|
+
"TeamNotFoundError",
|
|
108
|
+
# Materialization
|
|
109
|
+
"materialize_marketplace",
|
|
110
|
+
"MaterializedMarketplace",
|
|
111
|
+
"MaterializationError",
|
|
112
|
+
"GitNotAvailableError",
|
|
113
|
+
"InvalidMarketplaceError",
|
|
114
|
+
# Rendering
|
|
115
|
+
"render_settings",
|
|
116
|
+
"merge_settings",
|
|
117
|
+
"check_conflicts",
|
|
118
|
+
# Managed state
|
|
119
|
+
"ManagedState",
|
|
120
|
+
"load_managed_state",
|
|
121
|
+
"save_managed_state",
|
|
122
|
+
"clear_managed_state",
|
|
123
|
+
]
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Adapter for translating external org config format to internal Pydantic format.
|
|
3
|
+
|
|
4
|
+
This module implements the Anti-Corruption Layer pattern between:
|
|
5
|
+
- External format (JSON Schema): Human-readable, semver strings, nested organization
|
|
6
|
+
- Internal format (Pydantic): Python-native types, integer versioning, flat structure
|
|
7
|
+
|
|
8
|
+
The translation happens AFTER JSON Schema validation (Validation Gate) but
|
|
9
|
+
BEFORE Pydantic model_validate() to ensure proper type conversion.
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
from __future__ import annotations
|
|
13
|
+
|
|
14
|
+
import copy
|
|
15
|
+
from typing import Any
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def translate_org_config(external: dict[str, Any]) -> dict[str, Any]:
|
|
19
|
+
"""Translate external JSON format to internal Pydantic format.
|
|
20
|
+
|
|
21
|
+
External format (from org config JSON):
|
|
22
|
+
- organization.name, organization.id (nested)
|
|
23
|
+
- schema_version: "1.0.0" (semver string)
|
|
24
|
+
|
|
25
|
+
Internal format (for Pydantic):
|
|
26
|
+
- name (flat at root level)
|
|
27
|
+
- schema_version: 1 (integer, major version only)
|
|
28
|
+
|
|
29
|
+
Uses deepcopy to prevent side-effects on cached external configuration.
|
|
30
|
+
|
|
31
|
+
Args:
|
|
32
|
+
external: External org config dict (from cache or remote fetch)
|
|
33
|
+
|
|
34
|
+
Returns:
|
|
35
|
+
Internal format dict ready for Pydantic model_validate()
|
|
36
|
+
|
|
37
|
+
Examples:
|
|
38
|
+
>>> external = {
|
|
39
|
+
... "schema_version": "1.0.0",
|
|
40
|
+
... "organization": {"name": "Acme Corp", "id": "acme"},
|
|
41
|
+
... "profiles": {}
|
|
42
|
+
... }
|
|
43
|
+
>>> internal = translate_org_config(external)
|
|
44
|
+
>>> internal["name"]
|
|
45
|
+
'Acme Corp'
|
|
46
|
+
>>> internal["schema_version"]
|
|
47
|
+
1
|
|
48
|
+
"""
|
|
49
|
+
# Use deepcopy to prevent cache poisoning via shallow copy mutations
|
|
50
|
+
internal = copy.deepcopy(external)
|
|
51
|
+
|
|
52
|
+
# ── Flatten organization object ───────────────────────────────────────────
|
|
53
|
+
# Pop the nested organization structure, default to empty dict if missing
|
|
54
|
+
org_data = internal.pop("organization", {})
|
|
55
|
+
|
|
56
|
+
# Only map 'name' if it wasn't already at the top level (precedence rule)
|
|
57
|
+
# This handles the case where config is already in internal format
|
|
58
|
+
if "name" not in internal and "name" in org_data:
|
|
59
|
+
internal["name"] = org_data["name"]
|
|
60
|
+
|
|
61
|
+
# ── Convert semver string to integer ──────────────────────────────────────
|
|
62
|
+
raw_version = internal.get("schema_version")
|
|
63
|
+
if isinstance(raw_version, str):
|
|
64
|
+
# Remove common 'v' prefix if present (e.g., "v1.0.0" -> "1.0.0")
|
|
65
|
+
clean_version = raw_version.lstrip("vV")
|
|
66
|
+
try:
|
|
67
|
+
# Handle both "1.0.0" -> 1 and "1" -> 1
|
|
68
|
+
internal["schema_version"] = int(clean_version.split(".")[0])
|
|
69
|
+
except ValueError:
|
|
70
|
+
# If parsing fails, leave as-is; Pydantic will catch the type error
|
|
71
|
+
# and provide better error context than a generic ValueError
|
|
72
|
+
pass
|
|
73
|
+
|
|
74
|
+
return internal
|