validio-cli 7.5.0__tar.gz → 8.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (27) hide show
  1. {validio_cli-7.5.0 → validio_cli-8.1.0}/PKG-INFO +7 -3
  2. {validio_cli-7.5.0 → validio_cli-8.1.0}/pyproject.toml +1 -1
  3. {validio_cli-7.5.0 → validio_cli-8.1.0}/validio_cli/__init__.py +0 -8
  4. {validio_cli-7.5.0 → validio_cli-8.1.0}/validio_cli/bin/entities/code.py +23 -18
  5. {validio_cli-7.5.0 → validio_cli-8.1.0}/validio_cli/bin/entities/namespaces.py +77 -24
  6. {validio_cli-7.5.0 → validio_cli-8.1.0}/LICENSE +0 -0
  7. {validio_cli-7.5.0 → validio_cli-8.1.0}/README_PUBLIC.md +0 -0
  8. {validio_cli-7.5.0 → validio_cli-8.1.0}/validio_cli/bin/entities/channels.py +0 -0
  9. {validio_cli-7.5.0 → validio_cli-8.1.0}/validio_cli/bin/entities/config.py +0 -0
  10. {validio_cli-7.5.0 → validio_cli-8.1.0}/validio_cli/bin/entities/credentials.py +0 -0
  11. {validio_cli-7.5.0 → validio_cli-8.1.0}/validio_cli/bin/entities/dbt.py +0 -0
  12. {validio_cli-7.5.0 → validio_cli-8.1.0}/validio_cli/bin/entities/filters.py +0 -0
  13. {validio_cli-7.5.0 → validio_cli-8.1.0}/validio_cli/bin/entities/incidents.py +0 -0
  14. {validio_cli-7.5.0 → validio_cli-8.1.0}/validio_cli/bin/entities/metrics.py +0 -0
  15. {validio_cli-7.5.0 → validio_cli-8.1.0}/validio_cli/bin/entities/notification_rules.py +0 -0
  16. {validio_cli-7.5.0 → validio_cli-8.1.0}/validio_cli/bin/entities/resources.py +0 -0
  17. {validio_cli-7.5.0 → validio_cli-8.1.0}/validio_cli/bin/entities/segmentations.py +0 -0
  18. {validio_cli-7.5.0 → validio_cli-8.1.0}/validio_cli/bin/entities/segments.py +0 -0
  19. {validio_cli-7.5.0 → validio_cli-8.1.0}/validio_cli/bin/entities/sources.py +0 -0
  20. {validio_cli-7.5.0 → validio_cli-8.1.0}/validio_cli/bin/entities/users.py +0 -0
  21. {validio_cli-7.5.0 → validio_cli-8.1.0}/validio_cli/bin/entities/validators.py +0 -0
  22. {validio_cli-7.5.0 → validio_cli-8.1.0}/validio_cli/bin/entities/windows.py +0 -0
  23. {validio_cli-7.5.0 → validio_cli-8.1.0}/validio_cli/bin/main.py +0 -0
  24. {validio_cli-7.5.0 → validio_cli-8.1.0}/validio_cli/components.py +0 -0
  25. {validio_cli-7.5.0 → validio_cli-8.1.0}/validio_cli/metadata.py +0 -0
  26. {validio_cli-7.5.0 → validio_cli-8.1.0}/validio_cli/namespace.py +0 -0
  27. {validio_cli-7.5.0 → validio_cli-8.1.0}/validio_cli/schema.py +0 -0
@@ -1,9 +1,9 @@
1
- Metadata-Version: 2.1
1
+ Metadata-Version: 2.4
2
2
  Name: validio-cli
3
- Version: 7.5.0
3
+ Version: 8.1.0
4
4
  Summary: CLI tool to interact with the Validio platform
5
- Home-page: https://validio.io/
6
5
  License: Apache-2.0
6
+ License-File: LICENSE
7
7
  Author: Validio
8
8
  Author-email: support@validio.io
9
9
  Requires-Python: >=3.10,<4.0
@@ -11,6 +11,9 @@ Classifier: License :: OSI Approved :: Apache Software License
11
11
  Classifier: Programming Language :: Python :: 3
12
12
  Classifier: Programming Language :: Python :: 3.10
13
13
  Classifier: Programming Language :: Python :: 3.11
14
+ Classifier: Programming Language :: Python :: 3.12
15
+ Classifier: Programming Language :: Python :: 3.13
16
+ Classifier: Programming Language :: Python :: 3.14
14
17
  Requires-Dist: camel-converter (>=3.0.0,<4.0.0)
15
18
  Requires-Dist: classdiff (>=0.5.2,<0.6.0)
16
19
  Requires-Dist: prompt-toolkit (>=3.0.38,<4.0.0)
@@ -19,6 +22,7 @@ Requires-Dist: typer[all] (>=0.8.0,<0.16.0)
19
22
  Requires-Dist: types-tabulate (>=0.9.0.2,<0.10.0.0)
20
23
  Requires-Dist: validio-sdk
21
24
  Project-URL: Documentation, https://docs.validio.io/
25
+ Project-URL: Homepage, https://validio.io/
22
26
  Description-Content-Type: text/markdown
23
27
 
24
28
  # validio-cli
@@ -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 = "7.5.0"
6
+ version = "8.1.0"
7
7
  description = "CLI tool to interact with the Validio platform"
8
8
  authors = ["Validio <support@validio.io>"]
9
9
  license = "Apache-2.0"
@@ -25,14 +25,6 @@ T = TypeVar("T", bound="OutputSettings")
25
25
  DEFAULT_NAMESPACE: str = "default"
26
26
 
27
27
 
28
- class Role(str, Enum):
29
- """User roles."""
30
-
31
- ADMIN = "ADMIN"
32
- EDITOR = "EDITOR"
33
- VIEWER = "VIEWER"
34
-
35
-
36
28
  class OutputFormat(str, Enum):
37
29
  """Available output formats for the CLI"""
38
30
 
@@ -17,6 +17,7 @@ from validio_sdk.resource._resource import DiffContext, Resource, ResourceDeprec
17
17
  from validio_sdk.resource._util import SourceSchemaReinference
18
18
  from validio_sdk.resource.replacement import (
19
19
  ImmutableFieldReplacementReason,
20
+ RecreateOnChangeReplacementReason,
20
21
  ReplacementReason,
21
22
  )
22
23
 
@@ -101,7 +102,6 @@ update_all_schemas_option = typer.Option(
101
102
  show_progress_option = typer.Option(
102
103
  sys.stdout.isatty(),
103
104
  help=(
104
- f"{BETA_BANNER} "
105
105
  "If enabled, progress updates will be displayed during "
106
106
  "execution of this command"
107
107
  ),
@@ -111,8 +111,8 @@ show_progress_option = typer.Option(
111
111
  def target_option(resource_type: str) -> Any:
112
112
  return typer.Option(
113
113
  [],
114
+ f"--{resource_type}",
114
115
  help=(
115
- f"{BETA_BANNER} "
116
116
  f"Only target selected {resource_type.replace('-', ' ')}(s) and ignore "
117
117
  f"changes for others. E.g. `--{resource_type} resource-a"
118
118
  ),
@@ -148,6 +148,7 @@ async def plan(
148
148
  color: bool = color_option,
149
149
  channel: list[str] = target_option("channel"),
150
150
  credential: list[str] = target_option("credential"),
151
+ filter_: list[str] = target_option("filter"),
151
152
  notification_rule: list[str] = target_option("notification-rule"),
152
153
  segmentation: list[str] = target_option("segmentation"),
153
154
  source: list[str] = target_option("source"),
@@ -177,6 +178,7 @@ async def plan(
177
178
  targets=code_plan.ResourceNames(
178
179
  channels=set(channel),
179
180
  credentials=set(credential),
181
+ filters=set(filter_),
180
182
  notification_rules=set(notification_rule),
181
183
  segmentations=set(segmentation),
182
184
  sources=set(source),
@@ -204,6 +206,7 @@ async def apply(
204
206
  color: bool = color_option,
205
207
  channel: list[str] = target_option("channel"),
206
208
  credential: list[str] = target_option("credential"),
209
+ filter_: list[str] = target_option("filter"),
207
210
  notification_rule: list[str] = target_option("notification-rule"),
208
211
  segmentation: list[str] = target_option("segmentation"),
209
212
  source: list[str] = target_option("source"),
@@ -212,14 +215,12 @@ async def apply(
212
215
  progress: bool = show_progress_option,
213
216
  test_credentials: bool = typer.Option(
214
217
  True,
215
- help=(
216
- f"{BETA_BANNER} Test that credentials are valid before creating or updating"
217
- ),
218
+ help="Test that credentials are valid before creating or updating",
218
219
  ),
219
220
  dry_run_sql: bool = typer.Option(
220
221
  False,
221
- help=f"""
222
- {BETA_BANNER} Dry run SQL queries before creating/updating SQL Filters,
222
+ help="""
223
+ Dry run SQL queries before creating/updating SQL Filters,
223
224
  SQL Sources or SQL Validators.
224
225
  Note: this feature is unsupported on Azure based warehouses.
225
226
  """,
@@ -247,6 +248,7 @@ async def apply(
247
248
  targets=code_plan.ResourceNames(
248
249
  channels=set(channel),
249
250
  credentials=set(credential),
251
+ filters=set(filter_),
250
252
  notification_rules=set(notification_rule),
251
253
  segmentations=set(segmentation),
252
254
  sources=set(source),
@@ -307,21 +309,21 @@ async def _import(
307
309
  credential: list[str] = typer.Option(
308
310
  [],
309
311
  help=(
310
- f"{BETA_BANNER} Target credential resources to move into "
311
- f"the current project's namespace. "
312
- f"This moves any specified credentials together with their "
313
- f"attached sources, windows, filters, segmentations and validators. "
314
- f"Only applicable with '--import-namespace'"
312
+ "Target credential resources to move into "
313
+ "the current project's namespace. "
314
+ "This moves any specified credentials together with their "
315
+ "attached sources, windows, filters, segmentations and validators. "
316
+ "Only applicable with '--import-namespace'"
315
317
  ),
316
318
  ),
317
319
  channel: list[str] = typer.Option(
318
320
  [],
319
321
  help=(
320
- f"{BETA_BANNER} Target channel resources to move into "
321
- f"the current project's namespace. "
322
- f"This moves any specified channels together with their "
323
- f"attached notification rules. "
324
- f"Only applicable with '--import-namespace'"
322
+ "Target channel resources to move into "
323
+ "the current project's namespace. "
324
+ "This moves any specified channels together with their "
325
+ "attached notification rules. "
326
+ "Only applicable with '--import-namespace'"
325
327
  ),
326
328
  ),
327
329
  progress: bool = show_progress_option,
@@ -598,7 +600,10 @@ def _show_replace_resource_diff(
598
600
 
599
601
  rewrites = _diff_field_rewrites(show_schema)
600
602
  a_value, b_value = (None, None)
601
- if isinstance(reason, ImmutableFieldReplacementReason):
603
+ if isinstance(
604
+ reason,
605
+ (ImmutableFieldReplacementReason, RecreateOnChangeReplacementReason),
606
+ ):
602
607
  update = reason.resource_update
603
608
  a_value = code_plan._create_resource_diff_object(
604
609
  update.manifest,
@@ -1,5 +1,6 @@
1
- from typing import Optional
1
+ from typing import Any, Optional
2
2
 
3
+ import click
3
4
  import typer
4
5
  from validio_sdk import ValidioError
5
6
 
@@ -9,12 +10,27 @@ from validio_cli import (
9
10
  OutputFormat,
10
11
  OutputFormatOption,
11
12
  OutputSettings,
12
- Role,
13
13
  get_client,
14
14
  output_json,
15
15
  output_text,
16
16
  )
17
17
 
18
+
19
+ class RoleSuggestion(click.ParamType):
20
+ name = "role"
21
+
22
+ def get_metavar(self, _: click.Parameter) -> str | None:
23
+ return "[Admin|Editor|Viewer|<Custom>]"
24
+
25
+ def convert(
26
+ self,
27
+ value: Any,
28
+ _param: click.Parameter | None,
29
+ _ctx: click.Context | None,
30
+ ) -> Any:
31
+ return value
32
+
33
+
18
34
  app = AsyncTyper(help="Namespaces used to separate resources")
19
35
 
20
36
 
@@ -40,12 +56,16 @@ async def get(
40
56
  )
41
57
 
42
58
 
43
- @app.async_command(help="[red][bold]BETA[/bold][/red] Create a namespace")
59
+ @app.async_command(help="Create a namespace")
44
60
  async def create(
45
61
  config_dir: str = ConfigDir,
46
62
  output_format: OutputFormat = OutputFormatOption,
47
63
  identifier: str = typer.Argument(..., help="Namespace name"),
48
- role: Role = typer.Option(None, help="The role to assign --api-key and --member"),
64
+ role: str = typer.Option(
65
+ None,
66
+ click_type=RoleSuggestion(),
67
+ help="The role to assign --api-key and --member",
68
+ ),
49
69
  api_key: list[str] = typer.Option(
50
70
  [],
51
71
  "--api-key",
@@ -68,19 +88,23 @@ async def create(
68
88
  "--role is specified but no --member or --api-key to assign the role"
69
89
  )
70
90
 
91
+ role_configs = await vc.get_namespace_role_configs()
92
+
71
93
  ns_api_keys = [
72
94
  {
73
95
  "id": cfg.access_key,
74
- "role": Role.ADMIN,
96
+ "roleId": _find_admin_role_id(role_configs),
75
97
  }
76
98
  ]
77
99
 
78
- for ak in api_key:
79
- ns_api_keys.append({"id": ak, "role": role})
100
+ if role:
101
+ role_id = _find_role_id(role_configs, role)
102
+ for ak in api_key:
103
+ ns_api_keys.append({"id": ak, "roleId": role_id})
80
104
 
81
- ns_members = []
82
- for m in member:
83
- ns_members.append({"id": m, "role": role})
105
+ ns_members = [{"id": m, "roleId": role_id} for m in member]
106
+ else:
107
+ ns_members = []
84
108
 
85
109
  namespaces = await vc.create_namespace(
86
110
  namespace_id=identifier,
@@ -101,12 +125,16 @@ async def create(
101
125
  )
102
126
 
103
127
 
104
- @app.async_command(help="[red][bold]BETA[/bold][/red] Update a namespace")
128
+ @app.async_command(help="Update a namespace")
105
129
  async def update(
106
130
  config_dir: str = ConfigDir,
107
131
  output_format: OutputFormat = OutputFormatOption,
108
132
  identifier: str = typer.Argument(..., help="Namespace name"),
109
- role: Role = typer.Option(None, help="The role to assign --api-key and --member"),
133
+ role: str = typer.Option(
134
+ None,
135
+ click_type=RoleSuggestion(),
136
+ help="The role to assign --api-key and --member",
137
+ ),
110
138
  api_key: list[str] = typer.Option(
111
139
  [],
112
140
  "--api-key",
@@ -128,13 +156,16 @@ async def update(
128
156
  "--role is specified but no --member or --api-key to assign the role"
129
157
  )
130
158
 
131
- ns_api_keys = []
132
- for ak in api_key:
133
- ns_api_keys.append({"id": ak, "role": role})
159
+ role_configs = await vc.get_namespace_role_configs()
160
+ ns_api_keys, ns_members = [], []
134
161
 
135
- ns_members = []
136
- for m in member:
137
- ns_members.append({"id": m, "role": role})
162
+ if role:
163
+ role_id = _find_role_id(role_configs, role)
164
+ for ak in api_key:
165
+ ns_api_keys.append({"id": ak, "roleId": role_id})
166
+
167
+ for m in member:
168
+ ns_members.append({"id": m, "roleId": role_id})
138
169
 
139
170
  namespace = await vc.get_namespaces(identifier)
140
171
  if namespace is None:
@@ -144,15 +175,21 @@ async def update(
144
175
  raise ValidioError("failed to get namespace")
145
176
 
146
177
  api_keys = [
147
- {"id": x["apiKey"]["id"], "role": x["role"]}
148
- for x in namespace.get("apiKeys", {})
149
- if x["apiKey"]["id"] not in api_key
178
+ {
179
+ "id": ns_roles["apiKey"]["id"],
180
+ "roleId": _find_role_id(role_configs, ns_roles["role"]),
181
+ }
182
+ for ns_roles in namespace.get("apiKeys", {})
183
+ if ns_roles["apiKey"]["id"] not in api_key
150
184
  ] + ns_api_keys
151
185
 
152
186
  members = [
153
- {"id": x["user"]["id"], "role": x["role"]}
154
- for x in namespace["members"]
155
- if x["user"]["id"] not in member
187
+ {
188
+ "id": ns_roles["user"]["id"],
189
+ "roleId": _find_role_id(role_configs, ns_roles["role"]),
190
+ }
191
+ for ns_roles in namespace["members"]
192
+ if ns_roles["user"]["id"] not in member
156
193
  ] + ns_members
157
194
 
158
195
  namespaces = await vc.update_namespace_roles(
@@ -194,3 +231,19 @@ async def remove(
194
231
  "status": OutputSettings(pass_full_object=True, reformat=lambda _: status),
195
232
  },
196
233
  )
234
+
235
+
236
+ def _find_role_id(role_configs: list[dict[str, Any]], role_name: str) -> str:
237
+ for rc in role_configs:
238
+ if rc["name"].upper() == role_name.upper():
239
+ return rc["id"]
240
+
241
+ raise ValidioError(f"role '{role_name}' not found")
242
+
243
+
244
+ def _find_admin_role_id(role_configs: list[dict[str, Any]]) -> str:
245
+ for rc in role_configs:
246
+ if rc.get("isSystemRole") and rc["name"] == "Admin":
247
+ return rc["id"]
248
+
249
+ raise ValidioError("could not find system Admin role for namespace")
File without changes