validio-cli 0.25.2__tar.gz → 0.26.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.
Files changed (29) hide show
  1. {validio_cli-0.25.2 → validio_cli-0.26.1}/PKG-INFO +1 -1
  2. {validio_cli-0.25.2 → validio_cli-0.26.1}/pyproject.toml +4 -3
  3. {validio_cli-0.25.2 → validio_cli-0.26.1}/validio_cli/__init__.py +13 -2
  4. validio_cli-0.26.1/validio_cli/bin/entities/channels.py +124 -0
  5. {validio_cli-0.25.2 → validio_cli-0.26.1}/validio_cli/bin/entities/code.py +19 -12
  6. {validio_cli-0.25.2 → validio_cli-0.26.1}/validio_cli/bin/entities/credentials.py +46 -0
  7. {validio_cli-0.25.2 → validio_cli-0.26.1}/validio_cli/bin/entities/dbt.py +6 -33
  8. {validio_cli-0.25.2 → validio_cli-0.26.1}/validio_cli/bin/entities/filters.py +66 -0
  9. {validio_cli-0.25.2 → validio_cli-0.26.1}/validio_cli/bin/entities/incidents.py +7 -7
  10. {validio_cli-0.25.2 → validio_cli-0.26.1}/validio_cli/bin/entities/metrics.py +1 -1
  11. {validio_cli-0.25.2 → validio_cli-0.26.1}/validio_cli/bin/entities/namespaces.py +2 -1
  12. {validio_cli-0.25.2 → validio_cli-0.26.1}/validio_cli/bin/entities/notification_rules.py +31 -0
  13. {validio_cli-0.25.2 → validio_cli-0.26.1}/validio_cli/bin/entities/segmentations.py +31 -0
  14. {validio_cli-0.25.2 → validio_cli-0.26.1}/validio_cli/bin/entities/segments.py +2 -2
  15. {validio_cli-0.25.2 → validio_cli-0.26.1}/validio_cli/bin/entities/sources.py +35 -71
  16. {validio_cli-0.25.2 → validio_cli-0.26.1}/validio_cli/bin/entities/validators.py +31 -0
  17. {validio_cli-0.25.2 → validio_cli-0.26.1}/validio_cli/bin/entities/windows.py +39 -5
  18. {validio_cli-0.25.2 → validio_cli-0.26.1}/validio_cli/bin/main.py +3 -1
  19. validio_cli-0.25.2/validio_cli/bin/entities/channels.py +0 -57
  20. {validio_cli-0.25.2 → validio_cli-0.26.1}/LICENSE +0 -0
  21. {validio_cli-0.25.2 → validio_cli-0.26.1}/README_PUBLIC.md +0 -0
  22. {validio_cli-0.25.2 → validio_cli-0.26.1}/validio_cli/bin/entities/config.py +0 -0
  23. {validio_cli-0.25.2 → validio_cli-0.26.1}/validio_cli/bin/entities/recommendations.py +0 -0
  24. {validio_cli-0.25.2 → validio_cli-0.26.1}/validio_cli/bin/entities/resources.py +0 -0
  25. {validio_cli-0.25.2 → validio_cli-0.26.1}/validio_cli/bin/entities/users.py +0 -0
  26. {validio_cli-0.25.2 → validio_cli-0.26.1}/validio_cli/components.py +0 -0
  27. {validio_cli-0.25.2 → validio_cli-0.26.1}/validio_cli/metadata.py +0 -0
  28. {validio_cli-0.25.2 → validio_cli-0.26.1}/validio_cli/namespace.py +0 -0
  29. {validio_cli-0.25.2 → validio_cli-0.26.1}/validio_cli/schema.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: validio-cli
3
- Version: 0.25.2
3
+ Version: 0.26.1
4
4
  Summary: CLI tool to interact with the Validio platform
5
5
  Home-page: https://validio.io/
6
6
  License: Apache-2.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 = "0.25.2"
6
+ version = "0.26.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"
@@ -24,7 +24,7 @@ pydantic = "2.4.2"
24
24
  validio-sdk = "*"
25
25
 
26
26
  [tool.poetry.group.dev.dependencies]
27
- ruff = "^0.1.11"
27
+ ruff = "^0.9.6"
28
28
  mypy = "^1.2.0"
29
29
  licensecheck = "^2023.1.1"
30
30
  pytest = "^7.4.3"
@@ -41,6 +41,7 @@ validio = "validio_cli.bin.main:main"
41
41
  [tool.ruff]
42
42
  fix = true
43
43
 
44
+ [tool.ruff.lint]
44
45
  select = [
45
46
  "ARG", # flake8-unused-argument
46
47
  "C4", # flake8-comprehension
@@ -66,7 +67,7 @@ ignore = [
66
67
  "PLR0913", # `too-many-arguments` - Not relevant with only 6
67
68
  ]
68
69
 
69
- [tool.ruff.per-file-ignores]
70
+ [tool.ruff.lint.per-file-ignores]
70
71
  # No need for documentation in these files
71
72
  "__init__.py" = ["D"]
72
73
  "*/bin/*.py" = ["D"]
@@ -198,6 +198,17 @@ def output_text(items: Any, fields: dict[str, OutputSettings | None]) -> None:
198
198
  sys.stdout.flush()
199
199
 
200
200
 
201
+ def output_ok_or_error(result: Any) -> None:
202
+ status = "OK" if not result.get("errors") else "ERROR"
203
+
204
+ return output_text(
205
+ {"status": status},
206
+ fields={
207
+ "status": OutputSettings(pass_full_object=True, reformat=lambda _: status),
208
+ },
209
+ )
210
+
211
+
201
212
  # ruff: noqa: PLR2004, PLR0911
202
213
  def _format_relative_time(d: datetime) -> str:
203
214
  diff = datetime.now(timezone.utc) - d
@@ -214,11 +225,11 @@ def _format_relative_time(d: datetime) -> str:
214
225
  if seconds < 120:
215
226
  return "1m"
216
227
  if seconds < 3600:
217
- return f"{int(seconds/60)}m"
228
+ return f"{int(seconds / 60)}m"
218
229
  if seconds < 7200:
219
230
  return "1h"
220
231
 
221
- return f"{int(seconds/3600)}h"
232
+ return f"{int(seconds / 3600)}h"
222
233
 
223
234
 
224
235
  def _single_resource_if_specified(items: list[Any], identifier: str | None) -> Any:
@@ -0,0 +1,124 @@
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
+ Namespace,
12
+ OutputFormat,
13
+ OutputFormatOption,
14
+ OutputSettings,
15
+ ValidioConfig,
16
+ _single_resource_if_specified,
17
+ get_client,
18
+ output_json,
19
+ output_ok_or_error,
20
+ output_text,
21
+ )
22
+ from validio_cli.namespace import get_namespace
23
+
24
+ app = AsyncTyper(help="Channels used for notifications")
25
+
26
+
27
+ @app.async_command(help="Get channels")
28
+ async def get(
29
+ config_dir: str = ConfigDir,
30
+ output_format: OutputFormat = OutputFormatOption,
31
+ namespace: str = Namespace(),
32
+ identifier: str = Identifier,
33
+ ) -> None:
34
+ client, cfg = get_client(config_dir)
35
+
36
+ channels = await client.get_channels(
37
+ channel_id=identifier,
38
+ namespace_id=get_namespace(namespace, cfg),
39
+ )
40
+
41
+ # TODO(UI-2311): Fully support list/get/get_by_resource_name
42
+ if isinstance(channels, list):
43
+ channels = _single_resource_if_specified(channels, identifier)
44
+
45
+ if output_format == OutputFormat.JSON:
46
+ return output_json(channels, identifier)
47
+
48
+ return output_text(
49
+ channels,
50
+ fields={
51
+ "name": OutputSettings(attribute_name="resourceName"),
52
+ "type": OutputSettings(
53
+ attribute_name="__typename",
54
+ reformat=lambda x: to_snake(x.removesuffix("Channel")).upper(),
55
+ ),
56
+ "age": OutputSettings.string_as_datetime("createdAt"),
57
+ },
58
+ )
59
+
60
+
61
+ @app.async_command(help="Delete channel")
62
+ async def delete(
63
+ config_dir: str = ConfigDir,
64
+ output_format: OutputFormat = OutputFormatOption,
65
+ namespace: str = Namespace(),
66
+ identifiers: list[str] = Identifiers,
67
+ ) -> None:
68
+ if not identifiers:
69
+ raise ValidioError("no identifiers are provided")
70
+
71
+ vc, cfg = get_client(config_dir)
72
+
73
+ channel_ids = [
74
+ channel_id
75
+ for channel_id in [
76
+ await get_channel_id(vc, cfg, identifier, namespace)
77
+ for identifier in identifiers
78
+ ]
79
+ if channel_id
80
+ ]
81
+
82
+ result = await vc.delete_channels(channel_ids)
83
+
84
+ if output_format == OutputFormat.JSON:
85
+ return output_json(result)
86
+
87
+ return output_ok_or_error(result)
88
+
89
+
90
+ async def get_channel_id(
91
+ vc: APIClient, cfg: ValidioConfig, identifier: str, namespace: str
92
+ ) -> str | None:
93
+ """
94
+ Ensure the identifier is a resource id.
95
+
96
+ If it doesn't have the expected prefix, do a resource lookup by name.
97
+ """
98
+ identifier_type = "channel"
99
+ prefix = "CNL_"
100
+
101
+ if not identifier:
102
+ print(f"Missing {identifier_type} id or name")
103
+ return None
104
+
105
+ if identifier.startswith(prefix):
106
+ return identifier
107
+
108
+ resource = await vc.get_channels(
109
+ channel_id=identifier,
110
+ namespace_id=get_namespace(namespace, cfg),
111
+ )
112
+
113
+ if resource is None:
114
+ print(f"No {identifier_type} with name or id {identifier} found")
115
+ return None
116
+
117
+ if not isinstance(resource, dict):
118
+ raise ValidioError("failed to get channel id")
119
+
120
+ return resource["id"]
121
+
122
+
123
+ if __name__ == "__main__":
124
+ typer.run(app())
@@ -65,7 +65,7 @@ diff_option = typer.Option(
65
65
  )
66
66
 
67
67
  color_option = typer.Option(
68
- not os.environ.get("NO_COLOR", False),
68
+ not os.environ.get("NO_COLOR", False) and sys.stdout.isatty(),
69
69
  help="Enable colored output",
70
70
  )
71
71
 
@@ -99,7 +99,7 @@ update_all_schemas_option = typer.Option(
99
99
  )
100
100
 
101
101
  show_progress_option = typer.Option(
102
- True,
102
+ sys.stdout.isatty(),
103
103
  help=(
104
104
  f"{BETA_BANNER} "
105
105
  "If enabled, progress updates will be displayed during "
@@ -113,7 +113,7 @@ def target_option(resource_type: str) -> Any:
113
113
  [],
114
114
  help=(
115
115
  f"{BETA_BANNER} "
116
- f'Only target selected {resource_type.replace("-", " ")}(s) and ignore '
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
  ),
119
119
  )
@@ -210,6 +210,14 @@ async def apply(
210
210
  validator: list[str] = target_option("validator"),
211
211
  window: list[str] = target_option("window"),
212
212
  progress: bool = show_progress_option,
213
+ dry_run_sql: bool = typer.Option(
214
+ False,
215
+ help=f"""
216
+ {BETA_BANNER} Dry run SQL queries before creating/updating SQL Filters
217
+ or Validators.
218
+ Note: this feature is unsupported on Azure based warehouses.
219
+ """,
220
+ ),
213
221
  ) -> None:
214
222
  dir_path = directory_or_default(directory)
215
223
  client, _ = get_client(config_dir)
@@ -259,6 +267,7 @@ async def apply(
259
267
  diff=diff,
260
268
  show_secrets=show_secrets,
261
269
  show_progress=progress,
270
+ dry_run_sql=dry_run_sql,
262
271
  )
263
272
 
264
273
  print(
@@ -406,7 +415,7 @@ async def _plan(
406
415
  diff=plan_result.graph_diff,
407
416
  show_schema=show_schema,
408
417
  show_secrets=show_secrets,
409
- escape=sys.stdout.isatty() and color,
418
+ escape=color,
410
419
  diff_output=diff_output,
411
420
  )
412
421
 
@@ -533,9 +542,7 @@ def _show_update_resource_diff(
533
542
  cascade_update = ""
534
543
  if r.replacement_cascaded_update_parent:
535
544
  (cls, parent_name) = r.replacement_cascaded_update_parent
536
- cascade_update = (
537
- f" (due to replacement of " f"{cls.__name__}({parent_name!r}))"
538
- )
545
+ cascade_update = f" (due to replacement of {cls.__name__}({parent_name!r}))"
539
546
 
540
547
  print(f"{class_key} '{r.manifest.name}' will be updated{cascade_update}")
541
548
 
@@ -545,14 +552,14 @@ def _show_update_resource_diff(
545
552
  r.manifest,
546
553
  show_secrets=show_secrets,
547
554
  rewrites=rewrites,
548
- secrets_changed=r.secrets_changed,
555
+ secret_fields_changed=r.secret_fields_changed,
549
556
  is_manifest=True,
550
557
  )
551
558
  b_value = code_plan._create_resource_diff_object(
552
559
  r.server,
553
560
  show_secrets=show_secrets,
554
561
  rewrites=rewrites,
555
- secrets_changed=r.secrets_changed,
562
+ secret_fields_changed=r.secret_fields_changed,
556
563
  is_manifest=False,
557
564
  )
558
565
 
@@ -584,14 +591,14 @@ def _show_replace_resource_diff(
584
591
  update.manifest,
585
592
  show_secrets=show_secrets,
586
593
  rewrites=rewrites,
587
- secrets_changed=update.secrets_changed,
594
+ secret_fields_changed=update.secret_fields_changed,
588
595
  is_manifest=True,
589
596
  )
590
597
  b_value = code_plan._create_resource_diff_object(
591
598
  update.server,
592
599
  show_secrets=show_secrets,
593
600
  rewrites=rewrites,
594
- secrets_changed=update.secrets_changed,
601
+ secret_fields_changed=update.secret_fields_changed,
595
602
  is_manifest=False,
596
603
  )
597
604
  else:
@@ -647,7 +654,7 @@ def _show_deprecations(deprecations: list[ResourceDeprecation]) -> None:
647
654
  print()
648
655
  print(f"\033[93m{title}\033[0m") # Make the text yellow
649
656
  for i, message in enumerate(sorted(deprecations)):
650
- print(f"\033[93m{i+1:>2}. {message}\033[0m")
657
+ print(f"\033[93m{i + 1:>2}. {message}\033[0m")
651
658
 
652
659
  print(footer, end="")
653
660
 
@@ -7,6 +7,7 @@ from validio_cli import (
7
7
  AsyncTyper,
8
8
  ConfigDir,
9
9
  Identifier,
10
+ Identifiers,
10
11
  Namespace,
11
12
  OutputFormat,
12
13
  OutputFormatOption,
@@ -14,6 +15,7 @@ from validio_cli import (
14
15
  _single_resource_if_specified,
15
16
  get_client,
16
17
  output_json,
18
+ output_ok_or_error,
17
19
  output_text,
18
20
  )
19
21
  from validio_cli.namespace import get_namespace
@@ -52,6 +54,50 @@ async def get(
52
54
  )
53
55
 
54
56
 
57
+ @app.async_command(help="Delete credential")
58
+ async def delete(
59
+ config_dir: str = ConfigDir,
60
+ output_format: OutputFormat = OutputFormatOption,
61
+ namespace: str = Namespace(),
62
+ identifiers: list[str] = Identifiers,
63
+ ) -> None:
64
+ if not identifiers:
65
+ raise ValidioError("no identifiers are provided")
66
+
67
+ vc, cfg = get_client(config_dir)
68
+
69
+ credential_ids = [
70
+ credential_id
71
+ for credential_id in [
72
+ await get_credential_id(vc, cfg, identifier, namespace)
73
+ for identifier in identifiers
74
+ ]
75
+ if credential_id
76
+ ]
77
+
78
+ sources = await vc.get_sources(namespace_id=namespace)
79
+ if not isinstance(sources, list):
80
+ raise ValidioError("failed to get sources")
81
+
82
+ credentials_with_sources = {
83
+ s["credential"]["resourceName"]
84
+ for s in sources
85
+ if s["credential"]["id"] in credential_ids
86
+ }
87
+ if len(credentials_with_sources) > 0:
88
+ raise ValidioError(
89
+ "Following credentials are connected to sources:\n"
90
+ + "\n".join(credentials_with_sources)
91
+ )
92
+
93
+ result = await vc.delete_credentials(credential_ids)
94
+
95
+ if output_format == OutputFormat.JSON:
96
+ return output_json(result)
97
+
98
+ return output_ok_or_error(result)
99
+
100
+
55
101
  async def get_credential_id(
56
102
  vc: APIClient, cfg: ValidioConfig, identifier: str, namespace: str
57
103
  ) -> str | None:
@@ -1,6 +1,6 @@
1
1
  import typing
2
2
  from pathlib import Path
3
- from typing import Any, Optional, Tuple
3
+ from typing import Any, Optional
4
4
 
5
5
  import typer
6
6
  from validio_sdk import ValidioError, dbt, util
@@ -26,23 +26,8 @@ RUN_RESULTS_FILE_NAME = "run_results.json"
26
26
 
27
27
 
28
28
  def _resolve_file_paths(
29
- explicit_manifest_path: Optional[Path],
30
- explicit_run_result_path: Optional[Path],
31
- target_path: Optional[Path],
32
- ) -> Tuple[Path, Optional[Path]]:
33
- if explicit_manifest_path or explicit_run_result_path:
34
- print(
35
- "Deprecation notice ⚠️: The --manifest and --run-results flags "
36
- "have been deprecated and will be removed in a future version. "
37
- "Please use the --target-path argument instead."
38
- )
39
- if not explicit_manifest_path:
40
- raise ValidioError(
41
- "--manifest is required when combined with the --run-results argument"
42
- )
43
-
44
- return explicit_manifest_path, explicit_run_result_path
45
-
29
+ target_path: Path,
30
+ ) -> tuple[Path, Optional[Path]]:
46
31
  if not target_path:
47
32
  raise ValidioError("Missing --target-path argument")
48
33
 
@@ -66,23 +51,11 @@ async def upload(
66
51
  config_dir: str = ConfigDir,
67
52
  namespace: str = Namespace(),
68
53
  credential_id: str = typer.Option(..., help="Credential name or ID"),
69
- manifest: Optional[Path] = typer.Option(
70
- help="Path to the manifest file. "
71
- "(deprecated in favor of the --target-path flag)",
72
- default=None,
73
- ),
74
54
  job_name: str = typer.Option(
75
55
  ..., help="The job that the dbt execution belongs to, e.g. `staging-pipeline`"
76
56
  ),
77
- run_results: Optional[Path] = typer.Option(
78
- help="Path to the run results file. "
79
- "(deprecated in favor of the --target-path flag)",
80
- default=None,
81
- ),
82
- target_path: Optional[Path] = typer.Option(
83
- help="Path to the dbt artifacts target directory. "
84
- "(supersedes the --manifest flag)",
85
- default=None,
57
+ target_path: Path = typer.Option(
58
+ ..., help="Path to the dbt artifacts target directory. "
86
59
  ),
87
60
  ) -> None:
88
61
  client, cfg = get_client(config_dir)
@@ -93,7 +66,7 @@ async def upload(
93
66
  if resolved_credential_id is None:
94
67
  raise ValidioError(f"Credential '{credential_id}' not found")
95
68
 
96
- manifest, run_results = _resolve_file_paths(manifest, run_results, target_path)
69
+ manifest, run_results = _resolve_file_paths(target_path)
97
70
 
98
71
  run_results_content = None
99
72
  try:
@@ -3,15 +3,19 @@ from validio_sdk.exception import ValidioError
3
3
 
4
4
  import validio_cli
5
5
  from validio_cli import (
6
+ APIClient,
6
7
  AsyncTyper,
7
8
  ConfigDir,
8
9
  Identifier,
10
+ Identifiers,
9
11
  Namespace,
10
12
  OutputFormat,
11
13
  OutputFormatOption,
12
14
  OutputSettings,
15
+ ValidioConfig,
13
16
  get_client,
14
17
  output_json,
18
+ output_ok_or_error,
15
19
  output_text,
16
20
  )
17
21
  from validio_cli.namespace import get_namespace
@@ -62,5 +66,67 @@ async def get(
62
66
  )
63
67
 
64
68
 
69
+ @app.async_command(help="Delete filter")
70
+ async def delete(
71
+ config_dir: str = ConfigDir,
72
+ output_format: OutputFormat = OutputFormatOption,
73
+ namespace: str = Namespace(),
74
+ identifiers: list[str] = Identifiers,
75
+ ) -> None:
76
+ if not identifiers:
77
+ raise ValidioError("no identifiers are provided")
78
+
79
+ vc, cfg = get_client(config_dir)
80
+
81
+ filter_ids = [
82
+ filter_id
83
+ for filter_id in [
84
+ await get_filter_id(vc, cfg, identifier, namespace)
85
+ for identifier in identifiers
86
+ ]
87
+ if filter_id
88
+ ]
89
+
90
+ result = await vc.delete_filters(filter_ids)
91
+
92
+ if output_format == OutputFormat.JSON:
93
+ return output_json(result)
94
+
95
+ return output_ok_or_error(result)
96
+
97
+
98
+ async def get_filter_id(
99
+ vc: APIClient, cfg: ValidioConfig, identifier: str, namespace: str
100
+ ) -> str | None:
101
+ """
102
+ Ensure the identifier is a resource id.
103
+
104
+ If it doesn't have the expected prefix, do a resource lookup by name.
105
+ """
106
+ identifier_type = "filter"
107
+ prefix = "FTR_"
108
+
109
+ if not identifier:
110
+ print(f"Missing {identifier_type} id or name")
111
+ return None
112
+
113
+ if identifier.startswith(prefix):
114
+ return identifier
115
+
116
+ resource = await vc.get_filters(
117
+ filter_id=identifier,
118
+ namespace_id=get_namespace(namespace, cfg),
119
+ )
120
+
121
+ if resource is None:
122
+ print(f"No {identifier_type} with name or id {identifier} found")
123
+ return None
124
+
125
+ if not isinstance(resource, dict):
126
+ raise ValidioError("failed to get filter id")
127
+
128
+ return resource["id"]
129
+
130
+
65
131
  if __name__ == "__main__":
66
132
  typer.run(app())
@@ -80,8 +80,8 @@ async def get(
80
80
  "bound": OutputSettings(
81
81
  pass_full_object=True,
82
82
  reformat=lambda x: (
83
- f'{format_number(x["lowerBound"])} - '
84
- f'{format_number(x["upperBound"])}'
83
+ f"{format_number(x['lowerBound'])} - "
84
+ f"{format_number(x['upperBound'])}"
85
85
  ),
86
86
  ),
87
87
  "value": OutputSettings(reformat=format_number),
@@ -96,10 +96,10 @@ async def get(
96
96
  def calculate_operator(item: Any) -> str:
97
97
  type_ = item["__typename"][len("ValidatorMetricWith") :]
98
98
  if type_ == "DynamicThreshold":
99
- return f'{type_}/{item["decisionBoundsType"]}'
99
+ return f"{type_}/{item['decisionBoundsType']}"
100
100
 
101
101
  if type_ == "FixedThreshold":
102
- return f'{type_}/{item["operator"]}'
102
+ return f"{type_}/{item['operator']}"
103
103
 
104
104
  return type_
105
105
 
@@ -107,10 +107,10 @@ def calculate_operator(item: Any) -> str:
107
107
  def calculate_bound(item: Any) -> str:
108
108
  type_ = item["__typename"][len("ValidatorMetricWith") :]
109
109
  if type_ == "DynamicThreshold":
110
- bound = f'{item["lowerBound"]:.2f} - {item["upperBound"]:.2f}'
110
+ bound = f"{item['lowerBound']:.2f} - {item['upperBound']:.2f}"
111
111
  elif type_ == "DifferenceThreshold":
112
- lower = f'{item["lowerBound"]:.2f}' if item["lowerBound"] is not None else "-"
113
- upper = f'{item["upperBound"]:.2f}' if item["upperBound"] is not None else "-"
112
+ lower = f"{item['lowerBound']:.2f}" if item["lowerBound"] is not None else "-"
113
+ upper = f"{item['upperBound']:.2f}" if item["upperBound"] is not None else "-"
114
114
 
115
115
  bound = f"{lower} - {upper}"
116
116
  elif type_ == "FixedThreshold":
@@ -63,7 +63,7 @@ async def get(
63
63
  reformat=calculate_bound,
64
64
  ),
65
65
  "value": None,
66
- "is_incident": None,
66
+ "is_incident": OutputSettings(attribute_name="isIncident"),
67
67
  "age": OutputSettings.string_as_datetime(
68
68
  attribute_name="endTime",
69
69
  ),
@@ -34,7 +34,8 @@ async def get(
34
34
  return output_text(
35
35
  namespaces,
36
36
  fields={
37
- "name": OutputSettings(attribute_name="id"),
37
+ "id": None,
38
+ "name": None,
38
39
  },
39
40
  )
40
41
 
@@ -8,6 +8,7 @@ from validio_cli import (
8
8
  AsyncTyper,
9
9
  ConfigDir,
10
10
  Identifier,
11
+ Identifiers,
11
12
  Namespace,
12
13
  OutputFormat,
13
14
  OutputFormatOption,
@@ -15,6 +16,7 @@ from validio_cli import (
15
16
  _single_resource_if_specified,
16
17
  get_client,
17
18
  output_json,
19
+ output_ok_or_error,
18
20
  output_text,
19
21
  )
20
22
  from validio_cli.namespace import get_namespace
@@ -61,6 +63,35 @@ async def get(
61
63
  )
62
64
 
63
65
 
66
+ @app.async_command(help="Delete notification rule")
67
+ async def delete(
68
+ config_dir: str = ConfigDir,
69
+ output_format: OutputFormat = OutputFormatOption,
70
+ namespace: str = Namespace(),
71
+ identifiers: list[str] = Identifiers,
72
+ ) -> None:
73
+ if not identifiers:
74
+ raise ValidioError("no identifiers are provided")
75
+
76
+ vc, cfg = get_client(config_dir)
77
+
78
+ notification_rule_ids = [
79
+ notification_rule_id
80
+ for notification_rule_id in [
81
+ await get_notification_rule_id(vc, cfg, identifier, namespace)
82
+ for identifier in identifiers
83
+ ]
84
+ if notification_rule_id
85
+ ]
86
+
87
+ result = await vc.delete_notification_rules(notification_rule_ids)
88
+
89
+ if output_format == OutputFormat.JSON:
90
+ return output_json(result)
91
+
92
+ return output_ok_or_error(result)
93
+
94
+
64
95
  async def get_notification_rule_id(
65
96
  vc: APIClient, cfg: ValidioConfig, identifier: str, namespace: str
66
97
  ) -> str | None:
@@ -8,12 +8,14 @@ from validio_cli import (
8
8
  AsyncTyper,
9
9
  ConfigDir,
10
10
  Identifier,
11
+ Identifiers,
11
12
  Namespace,
12
13
  OutputFormat,
13
14
  OutputFormatOption,
14
15
  OutputSettings,
15
16
  get_client,
16
17
  output_json,
18
+ output_ok_or_error,
17
19
  output_text,
18
20
  )
19
21
  from validio_cli.namespace import get_namespace
@@ -65,6 +67,35 @@ async def get(
65
67
  )
66
68
 
67
69
 
70
+ @app.async_command(help="Delete segmentation")
71
+ async def delete(
72
+ config_dir: str = ConfigDir,
73
+ output_format: OutputFormat = OutputFormatOption,
74
+ namespace: str = Namespace(),
75
+ identifiers: list[str] = Identifiers,
76
+ ) -> None:
77
+ if not identifiers:
78
+ raise ValidioError("no identifiers are provided")
79
+
80
+ vc, cfg = get_client(config_dir)
81
+
82
+ segmentation_ids = [
83
+ segmentation_id
84
+ for segmentation_id in [
85
+ await get_segmentation_id(vc, cfg, identifier, namespace)
86
+ for identifier in identifiers
87
+ ]
88
+ if segmentation_id
89
+ ]
90
+
91
+ result = await vc.delete_segmentations(segmentation_ids)
92
+
93
+ if output_format == OutputFormat.JSON:
94
+ return output_json(result)
95
+
96
+ return output_ok_or_error(result)
97
+
98
+
68
99
  async def get_segmentation_id(
69
100
  vc: APIClient, cfg: ValidioConfig, identifier: str, namespace: str
70
101
  ) -> str | None:
@@ -46,11 +46,11 @@ async def get(
46
46
  for segment in segments:
47
47
  field_and_value = []
48
48
  for f in segment["fields"]:
49
- field_and_value.append(f'{f["field"]}={f["value"]}')
49
+ field_and_value.append(f"{f['field']}={f['value']}")
50
50
 
51
51
  additional = ""
52
52
  if len(field_and_value) > max_fields:
53
- additional = f" and {len(field_and_value)-max_fields} more"
53
+ additional = f" and {len(field_and_value) - max_fields} more"
54
54
  field_and_value = field_and_value[:max_fields]
55
55
 
56
56
  resources.append(
@@ -32,6 +32,7 @@ from validio_cli import (
32
32
  components,
33
33
  get_client,
34
34
  output_json,
35
+ output_ok_or_error,
35
36
  output_text,
36
37
  )
37
38
  from validio_cli.bin.entities import credentials
@@ -102,6 +103,35 @@ async def get(
102
103
  )
103
104
 
104
105
 
106
+ @app.async_command(help="Delete source")
107
+ async def delete(
108
+ config_dir: str = ConfigDir,
109
+ output_format: OutputFormat = OutputFormatOption,
110
+ namespace: str = Namespace(),
111
+ identifiers: list[str] = Identifiers,
112
+ ) -> None:
113
+ if not identifiers:
114
+ raise ValidioError("no identifiers are provided")
115
+
116
+ vc, cfg = get_client(config_dir)
117
+
118
+ source_ids = [
119
+ source_id
120
+ for source_id in [
121
+ await get_source_id(vc, cfg, identifier, namespace)
122
+ for identifier in identifiers
123
+ ]
124
+ if source_id
125
+ ]
126
+
127
+ result = await vc.delete_sources(source_ids)
128
+
129
+ if output_format == OutputFormat.JSON:
130
+ return output_json(result)
131
+
132
+ return output_ok_or_error(result)
133
+
134
+
105
135
  @app.async_command(help="Start sources")
106
136
  async def start(
107
137
  config_dir: str = ConfigDir,
@@ -208,6 +238,8 @@ def main(
208
238
  return None
209
239
 
210
240
 
241
+ # Not much to do with this complexity
242
+ # ruff: noqa: PLR0915,PLR0912
211
243
  async def _interactive(config_dir: str, filename: Path, namespace: str) -> None:
212
244
  vc, cfg = get_client(config_dir)
213
245
 
@@ -247,7 +279,6 @@ async def _interactive(config_dir: str, filename: Path, namespace: str) -> None:
247
279
  ("bigquery", "Google BigQuery"),
248
280
  ("gcs", "Google Cloud Storage"),
249
281
  ("pubsub", "Google Pub/Sub"),
250
- ("pubsub_lite", "Google Pub/Sub Lite"),
251
282
  ],
252
283
  "PostgreSqlCredential": [("postgresql", "PostgreSQL")],
253
284
  "RedshiftCredential": [("redshift", "Amazon Redshift")],
@@ -411,16 +442,6 @@ async def _interactive(config_dir: str, filename: Path, namespace: str) -> None:
411
442
  ("Message schema", [], ""),
412
443
  ]
413
444
  )
414
- await _infer_schema_pubsub_lite(
415
- vc,
416
- filename,
417
- credential_id,
418
- info.get("project", ""),
419
- info.get("subscription_id", ""),
420
- info.get("location", ""),
421
- info.get("message_format", ""),
422
- info.get("message_schema", ""),
423
- )
424
445
  case "kafka":
425
446
  info = await _multip_prompt(
426
447
  [
@@ -657,36 +678,6 @@ async def pubsub(
657
678
  )
658
679
 
659
680
 
660
- @infer_schema_app.async_command(help="Infer GCP Pub/Sub Lite schema")
661
- async def pubsub_lite(
662
- config_dir: str = ConfigDir,
663
- filename: Path = schema_filename_option("pubsub-lite"),
664
- credential_id: str = typer.Option(..., help="Credential name or ID"),
665
- project: str = typer.Option(..., help="GCP project"),
666
- subscription_id: str = typer.Option(..., help="GCP Pub/Sub subscription ID"),
667
- location: str = typer.Option(..., help="GCP Pub/Sub Lite location"),
668
- message_format: str = typer.Option(default=None, help="Message format"),
669
- message_schema: str = typer.Option(default=None, help="Message schema"),
670
- namespace: str = Namespace(),
671
- ) -> None:
672
- vc, cfg = get_client(config_dir)
673
-
674
- resolved_credential_id = await _resolve_credential(
675
- vc, cfg, credential_id, namespace
676
- )
677
-
678
- await _infer_schema_pubsub_lite(
679
- vc,
680
- filename,
681
- resolved_credential_id,
682
- project,
683
- subscription_id,
684
- location,
685
- message_format,
686
- message_schema,
687
- )
688
-
689
-
690
681
  @infer_schema_app.async_command(help="Infer Kafka schema")
691
682
  async def kafka(
692
683
  config_dir: str = ConfigDir,
@@ -934,30 +925,6 @@ async def _infer_schema_pubsub(
934
925
  _write_schema(filename, schema)
935
926
 
936
927
 
937
- async def _infer_schema_pubsub_lite(
938
- vc: APIClient,
939
- filename: Path,
940
- credential_id: str,
941
- project: str,
942
- subscription_id: str,
943
- location: str,
944
- message_format: str | None = None,
945
- message_schema: str | None = None,
946
- ) -> None:
947
- message_format_input = _get_message_format_input(message_format, message_schema)
948
- schema = await vc.infer_schema(
949
- class_name="GcpPubSubLite",
950
- variable_values={
951
- "credentialId": credential_id,
952
- "project": project,
953
- "subscriptionId": subscription_id,
954
- "location": location,
955
- "messageFormat": message_format_input,
956
- },
957
- )
958
- _write_schema(filename, schema)
959
-
960
-
961
928
  async def _infer_schema_kafka(
962
929
  vc: APIClient,
963
930
  filename: Path,
@@ -1199,13 +1166,10 @@ def _resource_filter(
1199
1166
  if credential_id is not None and source["credential"]["id"] != credential_id:
1200
1167
  return False
1201
1168
 
1202
- if (
1169
+ return (
1203
1170
  credential_name is not None
1204
- and source["credential"]["resourceName"] != credential_name
1205
- ):
1206
- return False
1207
-
1208
- return True
1171
+ and source["credential"]["resourceName"] == credential_name
1172
+ )
1209
1173
 
1210
1174
 
1211
1175
  async def _resolve_credential(
@@ -8,12 +8,14 @@ from validio_cli import (
8
8
  AsyncTyper,
9
9
  ConfigDir,
10
10
  Identifier,
11
+ Identifiers,
11
12
  Namespace,
12
13
  OutputFormat,
13
14
  OutputFormatOption,
14
15
  OutputSettings,
15
16
  get_client,
16
17
  output_json,
18
+ output_ok_or_error,
17
19
  output_text,
18
20
  )
19
21
  from validio_cli.bin.entities.sources import get_source_id
@@ -76,6 +78,35 @@ async def get(
76
78
  )
77
79
 
78
80
 
81
+ @app.async_command(help="Delete validator")
82
+ async def delete(
83
+ config_dir: str = ConfigDir,
84
+ output_format: OutputFormat = OutputFormatOption,
85
+ namespace: str = Namespace(),
86
+ identifiers: list[str] = Identifiers,
87
+ ) -> None:
88
+ if not identifiers:
89
+ raise ValidioError("no identifiers are provided")
90
+
91
+ vc, cfg = get_client(config_dir)
92
+
93
+ validator_ids = [
94
+ validator_id
95
+ for validator_id in [
96
+ await get_validator_id(vc, cfg, identifier, namespace)
97
+ for identifier in identifiers
98
+ ]
99
+ if validator_id
100
+ ]
101
+
102
+ result = await vc.delete_validators(validator_ids)
103
+
104
+ if output_format == OutputFormat.JSON:
105
+ return output_json(result)
106
+
107
+ return output_ok_or_error(result)
108
+
109
+
79
110
  async def get_validator_id(
80
111
  vc: APIClient, cfg: ValidioConfig, identifier: str, namespace: str
81
112
  ) -> str | None:
@@ -1,20 +1,22 @@
1
1
  import typer
2
2
  from camel_converter import to_snake
3
+ from validio_sdk._api.api import APIClient
3
4
  from validio_sdk.config import ValidioConfig
4
5
  from validio_sdk.exception import ValidioError
5
- from validio_sdk.validio_client import ValidioAPIClient
6
6
 
7
7
  import validio_cli
8
8
  from validio_cli import (
9
9
  AsyncTyper,
10
10
  ConfigDir,
11
11
  Identifier,
12
+ Identifiers,
12
13
  Namespace,
13
14
  OutputFormat,
14
15
  OutputFormatOption,
15
16
  OutputSettings,
16
17
  get_client,
17
18
  output_json,
19
+ output_ok_or_error,
18
20
  output_text,
19
21
  )
20
22
  from validio_cli.namespace import get_namespace
@@ -69,8 +71,37 @@ async def get(
69
71
  )
70
72
 
71
73
 
74
+ @app.async_command(help="Delete window")
75
+ async def delete(
76
+ config_dir: str = ConfigDir,
77
+ output_format: OutputFormat = OutputFormatOption,
78
+ namespace: str = Namespace(),
79
+ identifiers: list[str] = Identifiers,
80
+ ) -> None:
81
+ if not identifiers:
82
+ raise ValidioError("no identifiers are provided")
83
+
84
+ vc, cfg = get_client(config_dir)
85
+
86
+ window_ids = [
87
+ window_id
88
+ for window_id in [
89
+ await get_window_id(vc, cfg, identifier, namespace)
90
+ for identifier in identifiers
91
+ ]
92
+ if window_id
93
+ ]
94
+
95
+ result = await vc.delete_windows(window_ids)
96
+
97
+ if output_format == OutputFormat.JSON:
98
+ return output_json(result)
99
+
100
+ return output_ok_or_error(result)
101
+
102
+
72
103
  async def get_window_id(
73
- vc: ValidioAPIClient, cfg: ValidioConfig, identifier: str, namespace: str
104
+ vc: APIClient, cfg: ValidioConfig, identifier: str, namespace: str
74
105
  ) -> str | None:
75
106
  """
76
107
  Ensure the identifier is a resource id.
@@ -87,8 +118,8 @@ async def get_window_id(
87
118
  if identifier.startswith(prefix):
88
119
  return identifier
89
120
 
90
- resource = await vc.get_window_by_resource_name(
91
- resource_name=identifier,
121
+ resource = await vc.get_windows(
122
+ window_id=identifier,
92
123
  namespace_id=get_namespace(namespace, cfg),
93
124
  )
94
125
 
@@ -96,7 +127,10 @@ async def get_window_id(
96
127
  print(f"No {identifier_type} with name or id {identifier} found")
97
128
  return None
98
129
 
99
- return resource.id
130
+ if not isinstance(resource, dict):
131
+ raise ValidioError(f"failed to get {identifier_type} id")
132
+
133
+ return resource["id"]
100
134
 
101
135
 
102
136
  if __name__ == "__main__":
@@ -49,7 +49,9 @@ app.add_typer(metrics.app, no_args_is_help=True, name="metrics")
49
49
  app.add_typer(namespaces.app, no_args_is_help=True, name="namespaces")
50
50
  app.add_typer(resources.app, no_args_is_help=True, name="resources")
51
51
  app.add_typer(notification_rules.app, no_args_is_help=True, name="notification-rules")
52
- app.add_typer(recommendations.app, no_args_is_help=True, name="recommendations")
52
+ app.add_typer(
53
+ recommendations.app, no_args_is_help=True, name="recommendations", deprecated=True
54
+ )
53
55
  app.add_typer(segmentations.app, no_args_is_help=True, name="segmentations")
54
56
  app.add_typer(segments.app, no_args_is_help=True, name="segments")
55
57
  app.add_typer(sources.app, no_args_is_help=True, name="sources")
@@ -1,57 +0,0 @@
1
- import typer
2
- from camel_converter import to_snake
3
-
4
- from validio_cli import (
5
- AsyncTyper,
6
- ConfigDir,
7
- Identifier,
8
- Namespace,
9
- OutputFormat,
10
- OutputFormatOption,
11
- OutputSettings,
12
- _single_resource_if_specified,
13
- get_client,
14
- output_json,
15
- output_text,
16
- )
17
- from validio_cli.namespace import get_namespace
18
-
19
- app = AsyncTyper(help="Channels used for notifications")
20
-
21
-
22
- @app.async_command(help="Get channels")
23
- async def get(
24
- config_dir: str = ConfigDir,
25
- output_format: OutputFormat = OutputFormatOption,
26
- namespace: str = Namespace(),
27
- identifier: str = Identifier,
28
- ) -> None:
29
- client, cfg = get_client(config_dir)
30
-
31
- channels = await client.get_channels(
32
- channel_id=identifier,
33
- namespace_id=get_namespace(namespace, cfg),
34
- )
35
-
36
- # TODO(UI-2311): Fully support list/get/get_by_resource_name
37
- if isinstance(channels, list):
38
- channels = _single_resource_if_specified(channels, identifier)
39
-
40
- if output_format == OutputFormat.JSON:
41
- return output_json(channels, identifier)
42
-
43
- return output_text(
44
- channels,
45
- fields={
46
- "name": OutputSettings(attribute_name="resourceName"),
47
- "type": OutputSettings(
48
- attribute_name="__typename",
49
- reformat=lambda x: to_snake(x.removesuffix("Channel")).upper(),
50
- ),
51
- "age": OutputSettings.string_as_datetime("createdAt"),
52
- },
53
- )
54
-
55
-
56
- if __name__ == "__main__":
57
- typer.run(app())
File without changes