validio-cli 8.1.0__tar.gz → 9.0.1__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.
- {validio_cli-8.1.0 → validio_cli-9.0.1}/PKG-INFO +1 -1
- {validio_cli-8.1.0 → validio_cli-9.0.1}/pyproject.toml +1 -1
- {validio_cli-8.1.0 → validio_cli-9.0.1}/validio_cli/__init__.py +20 -0
- {validio_cli-8.1.0 → validio_cli-9.0.1}/validio_cli/bin/entities/code.py +53 -2
- validio_cli-9.0.1/validio_cli/bin/entities/integrations.py +109 -0
- {validio_cli-8.1.0 → validio_cli-9.0.1}/validio_cli/bin/entities/namespaces.py +28 -4
- {validio_cli-8.1.0 → validio_cli-9.0.1}/validio_cli/bin/main.py +24 -0
- {validio_cli-8.1.0 → validio_cli-9.0.1}/LICENSE +0 -0
- {validio_cli-8.1.0 → validio_cli-9.0.1}/README_PUBLIC.md +0 -0
- {validio_cli-8.1.0 → validio_cli-9.0.1}/validio_cli/bin/entities/channels.py +0 -0
- {validio_cli-8.1.0 → validio_cli-9.0.1}/validio_cli/bin/entities/config.py +0 -0
- {validio_cli-8.1.0 → validio_cli-9.0.1}/validio_cli/bin/entities/credentials.py +0 -0
- {validio_cli-8.1.0 → validio_cli-9.0.1}/validio_cli/bin/entities/dbt.py +0 -0
- {validio_cli-8.1.0 → validio_cli-9.0.1}/validio_cli/bin/entities/filters.py +0 -0
- {validio_cli-8.1.0 → validio_cli-9.0.1}/validio_cli/bin/entities/incidents.py +0 -0
- {validio_cli-8.1.0 → validio_cli-9.0.1}/validio_cli/bin/entities/metrics.py +0 -0
- {validio_cli-8.1.0 → validio_cli-9.0.1}/validio_cli/bin/entities/notification_rules.py +0 -0
- {validio_cli-8.1.0 → validio_cli-9.0.1}/validio_cli/bin/entities/resources.py +0 -0
- {validio_cli-8.1.0 → validio_cli-9.0.1}/validio_cli/bin/entities/segmentations.py +0 -0
- {validio_cli-8.1.0 → validio_cli-9.0.1}/validio_cli/bin/entities/segments.py +0 -0
- {validio_cli-8.1.0 → validio_cli-9.0.1}/validio_cli/bin/entities/sources.py +0 -0
- {validio_cli-8.1.0 → validio_cli-9.0.1}/validio_cli/bin/entities/users.py +0 -0
- {validio_cli-8.1.0 → validio_cli-9.0.1}/validio_cli/bin/entities/validators.py +0 -0
- {validio_cli-8.1.0 → validio_cli-9.0.1}/validio_cli/bin/entities/windows.py +0 -0
- {validio_cli-8.1.0 → validio_cli-9.0.1}/validio_cli/components.py +0 -0
- {validio_cli-8.1.0 → validio_cli-9.0.1}/validio_cli/metadata.py +0 -0
- {validio_cli-8.1.0 → validio_cli-9.0.1}/validio_cli/namespace.py +0 -0
- {validio_cli-8.1.0 → validio_cli-9.0.1}/validio_cli/schema.py +0 -0
|
@@ -3,7 +3,7 @@ name = "validio-cli"
|
|
|
3
3
|
# This version does not represent the released version or any tag. For each
|
|
4
4
|
# release we automatically bump this before building and publishing so this
|
|
5
5
|
# should be kept at 0.0.1dev1
|
|
6
|
-
version = "
|
|
6
|
+
version = "9.0.1"
|
|
7
7
|
description = "CLI tool to interact with the Validio platform"
|
|
8
8
|
authors = ["Validio <support@validio.io>"]
|
|
9
9
|
license = "Apache-2.0"
|
|
@@ -17,6 +17,7 @@ from tabulate import tabulate
|
|
|
17
17
|
from validio_sdk import config
|
|
18
18
|
from validio_sdk._api.api import APIClient
|
|
19
19
|
from validio_sdk.config import Config, ValidioConfig
|
|
20
|
+
from validio_sdk.features import Feature, all_features, set_enabled
|
|
20
21
|
from validio_sdk.util import ClassJSONEncoder
|
|
21
22
|
|
|
22
23
|
F = TypeVar("F", bound=Callable[..., Any])
|
|
@@ -267,3 +268,22 @@ def _resource_filter(
|
|
|
267
268
|
def _fixed_width(message: str, width: int = 25) -> str:
|
|
268
269
|
message = f"{message}: "
|
|
269
270
|
return f"{message:<{width}} "
|
|
271
|
+
|
|
272
|
+
|
|
273
|
+
def _apply_feature_flags(feature: list[Feature], all_features_flag: bool) -> None:
|
|
274
|
+
"""Apply feature-flag selection from CLI options.
|
|
275
|
+
|
|
276
|
+
Shared between the root callback and sub-app callbacks so users can pass
|
|
277
|
+
``--feature`` / ``--all-features`` at any position. The helper only mutates
|
|
278
|
+
the env var when the current callback actually received flag values — this
|
|
279
|
+
way a root-level selection isn't clobbered by a downstream callback that
|
|
280
|
+
was invoked without flags.
|
|
281
|
+
"""
|
|
282
|
+
if all_features_flag:
|
|
283
|
+
set_enabled(all_features())
|
|
284
|
+
return
|
|
285
|
+
|
|
286
|
+
if not feature:
|
|
287
|
+
return
|
|
288
|
+
|
|
289
|
+
set_enabled(frozenset(feature))
|
|
@@ -12,8 +12,14 @@ from validio_sdk.code import _import as code_import
|
|
|
12
12
|
from validio_sdk.code import apply as code_apply
|
|
13
13
|
from validio_sdk.code import plan as code_plan
|
|
14
14
|
from validio_sdk.code import scaffold
|
|
15
|
+
from validio_sdk.features import Feature
|
|
15
16
|
from validio_sdk.resource._diff import GraphDiff, ResourceUpdate
|
|
16
|
-
from validio_sdk.resource._resource import
|
|
17
|
+
from validio_sdk.resource._resource import (
|
|
18
|
+
DiffContext,
|
|
19
|
+
Resource,
|
|
20
|
+
ResourceDeprecation,
|
|
21
|
+
ResourceFeatureNotice,
|
|
22
|
+
)
|
|
17
23
|
from validio_sdk.resource._util import SourceSchemaReinference
|
|
18
24
|
from validio_sdk.resource.replacement import (
|
|
19
25
|
ImmutableFieldReplacementReason,
|
|
@@ -21,7 +27,13 @@ from validio_sdk.resource.replacement import (
|
|
|
21
27
|
ReplacementReason,
|
|
22
28
|
)
|
|
23
29
|
|
|
24
|
-
from validio_cli import
|
|
30
|
+
from validio_cli import (
|
|
31
|
+
AsyncTyper,
|
|
32
|
+
ConfigDir,
|
|
33
|
+
Namespace,
|
|
34
|
+
_apply_feature_flags,
|
|
35
|
+
get_client,
|
|
36
|
+
)
|
|
25
37
|
from validio_cli.bin.entities.resources import ResourceNamesToMove, do_move
|
|
26
38
|
from validio_cli.components import BETA_BANNER, proceed_with_operation
|
|
27
39
|
|
|
@@ -31,6 +43,25 @@ MAX_DEPRECATIONS_TO_SHOW = 20
|
|
|
31
43
|
app = AsyncTyper(help="Plan or apply your configuration")
|
|
32
44
|
|
|
33
45
|
|
|
46
|
+
@app.callback()
|
|
47
|
+
def _configure(
|
|
48
|
+
feature: list[Feature] = typer.Option(
|
|
49
|
+
[],
|
|
50
|
+
"--feature",
|
|
51
|
+
help=(
|
|
52
|
+
"Enable an opt-in feature. Repeatable. Features may change or be "
|
|
53
|
+
"removed at any time."
|
|
54
|
+
),
|
|
55
|
+
),
|
|
56
|
+
all_features_flag: bool = typer.Option(
|
|
57
|
+
False,
|
|
58
|
+
"--all-features",
|
|
59
|
+
help="Enable every opt-in feature.",
|
|
60
|
+
),
|
|
61
|
+
) -> None:
|
|
62
|
+
_apply_feature_flags(feature=feature, all_features_flag=all_features_flag)
|
|
63
|
+
|
|
64
|
+
|
|
34
65
|
class DiffOutput(str, Enum):
|
|
35
66
|
"""Available output formats for the CLI"""
|
|
36
67
|
|
|
@@ -436,6 +467,7 @@ async def _plan(
|
|
|
436
467
|
)
|
|
437
468
|
|
|
438
469
|
_show_deprecations(plan_result.deprecations)
|
|
470
|
+
_show_feature_notices(plan_result.feature_notices)
|
|
439
471
|
|
|
440
472
|
return plan_result
|
|
441
473
|
|
|
@@ -676,6 +708,25 @@ def _show_deprecations(deprecations: list[ResourceDeprecation]) -> None:
|
|
|
676
708
|
print(footer, end="")
|
|
677
709
|
|
|
678
710
|
|
|
711
|
+
def _show_feature_notices(notices: list[ResourceFeatureNotice]) -> None:
|
|
712
|
+
if not notices:
|
|
713
|
+
return
|
|
714
|
+
|
|
715
|
+
title = "⚠️ FEATURES IN USE"
|
|
716
|
+
footer = ""
|
|
717
|
+
|
|
718
|
+
if len(notices) > MAX_DEPRECATIONS_TO_SHOW:
|
|
719
|
+
footer = f"And {len(notices) - MAX_DEPRECATIONS_TO_SHOW} more...\n"
|
|
720
|
+
notices = notices[:MAX_DEPRECATIONS_TO_SHOW]
|
|
721
|
+
|
|
722
|
+
print()
|
|
723
|
+
print(f"\033[93m{title}\033[0m")
|
|
724
|
+
for i, message in enumerate(sorted(notices)):
|
|
725
|
+
print(f"\033[93m{i + 1:>2}. {message}\033[0m")
|
|
726
|
+
|
|
727
|
+
print(footer, end="")
|
|
728
|
+
|
|
729
|
+
|
|
679
730
|
def create_source_schema_reinference(
|
|
680
731
|
schemas_to_update: list[str], update_all_schemas: bool
|
|
681
732
|
) -> SourceSchemaReinference:
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import typer
|
|
2
|
+
from camel_converter import to_snake
|
|
3
|
+
from validio_sdk.exception import ValidioError
|
|
4
|
+
|
|
5
|
+
from validio_cli import (
|
|
6
|
+
APIClient,
|
|
7
|
+
AsyncTyper,
|
|
8
|
+
ConfigDir,
|
|
9
|
+
Identifier,
|
|
10
|
+
Identifiers,
|
|
11
|
+
OutputFormat,
|
|
12
|
+
OutputFormatOption,
|
|
13
|
+
OutputSettings,
|
|
14
|
+
_single_resource_if_specified,
|
|
15
|
+
get_client,
|
|
16
|
+
output_json,
|
|
17
|
+
output_ok_or_error,
|
|
18
|
+
output_text,
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
app = AsyncTyper(help="Integrations")
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
@app.async_command(help="Get integrations")
|
|
25
|
+
async def get(
|
|
26
|
+
config_dir: str = ConfigDir,
|
|
27
|
+
output_format: OutputFormat = OutputFormatOption,
|
|
28
|
+
identifier: str = Identifier,
|
|
29
|
+
) -> None:
|
|
30
|
+
client, _ = get_client(config_dir)
|
|
31
|
+
|
|
32
|
+
integrations = await client.get_integrations(integration_id=identifier)
|
|
33
|
+
|
|
34
|
+
if isinstance(integrations, list):
|
|
35
|
+
integrations = _single_resource_if_specified(integrations, identifier)
|
|
36
|
+
|
|
37
|
+
if output_format == OutputFormat.JSON:
|
|
38
|
+
return output_json(integrations, identifier)
|
|
39
|
+
|
|
40
|
+
return output_text(
|
|
41
|
+
integrations,
|
|
42
|
+
fields={
|
|
43
|
+
"name": OutputSettings(attribute_name="resourceName"),
|
|
44
|
+
"type": OutputSettings(
|
|
45
|
+
attribute_name="__typename",
|
|
46
|
+
reformat=lambda x: to_snake(x.removesuffix("Integration")).upper(),
|
|
47
|
+
),
|
|
48
|
+
"age": OutputSettings.string_as_datetime("createdAt"),
|
|
49
|
+
},
|
|
50
|
+
)
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
@app.async_command(help="Delete integration")
|
|
54
|
+
async def delete(
|
|
55
|
+
config_dir: str = ConfigDir,
|
|
56
|
+
output_format: OutputFormat = OutputFormatOption,
|
|
57
|
+
identifiers: list[str] = Identifiers,
|
|
58
|
+
) -> None:
|
|
59
|
+
if not identifiers:
|
|
60
|
+
raise ValidioError("no identifiers are provided")
|
|
61
|
+
|
|
62
|
+
vc, _ = get_client(config_dir)
|
|
63
|
+
|
|
64
|
+
integration_ids = [
|
|
65
|
+
integration_id
|
|
66
|
+
for integration_id in [
|
|
67
|
+
await get_integration_id(vc, identifier) for identifier in identifiers
|
|
68
|
+
]
|
|
69
|
+
if integration_id
|
|
70
|
+
]
|
|
71
|
+
|
|
72
|
+
result = await vc.delete_integrations(integration_ids)
|
|
73
|
+
|
|
74
|
+
if output_format == OutputFormat.JSON:
|
|
75
|
+
return output_json(result)
|
|
76
|
+
|
|
77
|
+
return output_ok_or_error(result)
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
async def get_integration_id(vc: APIClient, identifier: str) -> str | None:
|
|
81
|
+
"""
|
|
82
|
+
Ensure the identifier is a resource id.
|
|
83
|
+
|
|
84
|
+
If it doesn't have the expected prefix, do a resource lookup by name.
|
|
85
|
+
"""
|
|
86
|
+
identifier_type = "integration"
|
|
87
|
+
prefix = "INT_"
|
|
88
|
+
|
|
89
|
+
if not identifier:
|
|
90
|
+
print(f"Missing {identifier_type} id or name")
|
|
91
|
+
return None
|
|
92
|
+
|
|
93
|
+
if identifier.startswith(prefix):
|
|
94
|
+
return identifier
|
|
95
|
+
|
|
96
|
+
resource = await vc.get_integrations(integration_id=identifier)
|
|
97
|
+
|
|
98
|
+
if resource is None:
|
|
99
|
+
print(f"No {identifier_type} with name or id {identifier} found")
|
|
100
|
+
return None
|
|
101
|
+
|
|
102
|
+
if not isinstance(resource, dict):
|
|
103
|
+
raise ValidioError("failed to get integration id")
|
|
104
|
+
|
|
105
|
+
return resource["id"]
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
if __name__ == "__main__":
|
|
109
|
+
typer.run(app())
|
|
@@ -64,7 +64,7 @@ async def create(
|
|
|
64
64
|
role: str = typer.Option(
|
|
65
65
|
None,
|
|
66
66
|
click_type=RoleSuggestion(),
|
|
67
|
-
help="The role to assign --api-key and --member",
|
|
67
|
+
help="The role (name or id) to assign --api-key and --member",
|
|
68
68
|
),
|
|
69
69
|
api_key: list[str] = typer.Option(
|
|
70
70
|
[],
|
|
@@ -113,6 +113,8 @@ async def create(
|
|
|
113
113
|
api_keys=ns_api_keys,
|
|
114
114
|
)
|
|
115
115
|
|
|
116
|
+
_raise_on_errors(namespaces)
|
|
117
|
+
|
|
116
118
|
if output_format == OutputFormat.JSON:
|
|
117
119
|
return output_json(namespaces, identifier)
|
|
118
120
|
|
|
@@ -133,7 +135,7 @@ async def update(
|
|
|
133
135
|
role: str = typer.Option(
|
|
134
136
|
None,
|
|
135
137
|
click_type=RoleSuggestion(),
|
|
136
|
-
help="The role to assign --api-key and --member",
|
|
138
|
+
help="The role (name or id) to assign --api-key and --member",
|
|
137
139
|
),
|
|
138
140
|
api_key: list[str] = typer.Option(
|
|
139
141
|
[],
|
|
@@ -198,6 +200,8 @@ async def update(
|
|
|
198
200
|
api_keys=api_keys,
|
|
199
201
|
)
|
|
200
202
|
|
|
203
|
+
_raise_on_errors(namespaces)
|
|
204
|
+
|
|
201
205
|
if output_format == OutputFormat.JSON:
|
|
202
206
|
return output_json(namespaces, identifier)
|
|
203
207
|
|
|
@@ -234,11 +238,16 @@ async def remove(
|
|
|
234
238
|
|
|
235
239
|
|
|
236
240
|
def _find_role_id(role_configs: list[dict[str, Any]], role_name: str) -> str:
|
|
241
|
+
# Accept either the role id or a case-insensitive role name — the server
|
|
242
|
+
# returns `role` as a scalar on existing members/apiKeys and the format is
|
|
243
|
+
# not guaranteed, so we tolerate both.
|
|
237
244
|
for rc in role_configs:
|
|
238
|
-
if rc["name"].
|
|
245
|
+
if rc["id"] == role_name or rc["name"].casefold() == role_name.casefold():
|
|
239
246
|
return rc["id"]
|
|
240
247
|
|
|
241
|
-
|
|
248
|
+
available = ", ".join(sorted(rc["name"] for rc in role_configs))
|
|
249
|
+
|
|
250
|
+
raise ValidioError(f"role '{role_name}' not found. Available roles: {available}")
|
|
242
251
|
|
|
243
252
|
|
|
244
253
|
def _find_admin_role_id(role_configs: list[dict[str, Any]]) -> str:
|
|
@@ -247,3 +256,18 @@ def _find_admin_role_id(role_configs: list[dict[str, Any]]) -> str:
|
|
|
247
256
|
return rc["id"]
|
|
248
257
|
|
|
249
258
|
raise ValidioError("could not find system Admin role for namespace")
|
|
259
|
+
|
|
260
|
+
|
|
261
|
+
def _raise_on_errors(response: Any) -> None:
|
|
262
|
+
if not isinstance(response, dict):
|
|
263
|
+
return
|
|
264
|
+
|
|
265
|
+
errors = response.get("errors") or []
|
|
266
|
+
if not errors:
|
|
267
|
+
return
|
|
268
|
+
|
|
269
|
+
messages = "; ".join(
|
|
270
|
+
f"{e.get('code', 'UNKNOWN')}: {e.get('message', '')}" for e in errors
|
|
271
|
+
)
|
|
272
|
+
|
|
273
|
+
raise ValidioError(f"server returned errors: {messages}")
|
|
@@ -8,8 +8,10 @@ import validio_sdk.metadata
|
|
|
8
8
|
from tabulate import tabulate
|
|
9
9
|
from validio_sdk import ValidioError
|
|
10
10
|
from validio_sdk.config import Config
|
|
11
|
+
from validio_sdk.features import Feature
|
|
11
12
|
|
|
12
13
|
import validio_cli.metadata
|
|
14
|
+
from validio_cli import _apply_feature_flags
|
|
13
15
|
from validio_cli.bin.entities import (
|
|
14
16
|
channels,
|
|
15
17
|
code,
|
|
@@ -18,6 +20,7 @@ from validio_cli.bin.entities import (
|
|
|
18
20
|
dbt,
|
|
19
21
|
filters,
|
|
20
22
|
incidents,
|
|
23
|
+
integrations,
|
|
21
24
|
metrics,
|
|
22
25
|
namespaces,
|
|
23
26
|
notification_rules,
|
|
@@ -37,6 +40,26 @@ app = typer.Typer(
|
|
|
37
40
|
rich_markup_mode="rich",
|
|
38
41
|
)
|
|
39
42
|
|
|
43
|
+
|
|
44
|
+
@app.callback()
|
|
45
|
+
def _configure(
|
|
46
|
+
feature: list[Feature] = typer.Option(
|
|
47
|
+
[],
|
|
48
|
+
"--feature",
|
|
49
|
+
help=(
|
|
50
|
+
"Enable an opt-in feature. Repeatable. Features may change or be "
|
|
51
|
+
"removed at any time."
|
|
52
|
+
),
|
|
53
|
+
),
|
|
54
|
+
all_features_flag: bool = typer.Option(
|
|
55
|
+
False,
|
|
56
|
+
"--all-features",
|
|
57
|
+
help="Enable every opt-in feature.",
|
|
58
|
+
),
|
|
59
|
+
) -> None:
|
|
60
|
+
_apply_feature_flags(feature=feature, all_features_flag=all_features_flag)
|
|
61
|
+
|
|
62
|
+
|
|
40
63
|
app.add_typer(channels.app, no_args_is_help=True, name="channels")
|
|
41
64
|
app.add_typer(code.app, no_args_is_help=True, name="code")
|
|
42
65
|
app.add_typer(config.app, no_args_is_help=True, name="config")
|
|
@@ -44,6 +67,7 @@ app.add_typer(credentials.app, no_args_is_help=True, name="credentials")
|
|
|
44
67
|
app.add_typer(dbt.app, no_args_is_help=True, name="dbt")
|
|
45
68
|
app.add_typer(filters.app, no_args_is_help=True, name="filters")
|
|
46
69
|
app.add_typer(incidents.app, no_args_is_help=True, name="incidents")
|
|
70
|
+
app.add_typer(integrations.app, no_args_is_help=True, name="integrations")
|
|
47
71
|
app.add_typer(metrics.app, no_args_is_help=True, name="metrics")
|
|
48
72
|
app.add_typer(namespaces.app, no_args_is_help=True, name="namespaces")
|
|
49
73
|
app.add_typer(resources.app, no_args_is_help=True, name="resources")
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|