validio-cli 5.2.0__tar.gz → 5.3.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-5.2.0 → validio_cli-5.3.0}/PKG-INFO +1 -1
  2. {validio_cli-5.2.0 → validio_cli-5.3.0}/pyproject.toml +1 -1
  3. {validio_cli-5.2.0 → validio_cli-5.3.0}/validio_cli/__init__.py +6 -3
  4. {validio_cli-5.2.0 → validio_cli-5.3.0}/validio_cli/bin/entities/sources.py +128 -17
  5. {validio_cli-5.2.0 → validio_cli-5.3.0}/LICENSE +0 -0
  6. {validio_cli-5.2.0 → validio_cli-5.3.0}/README_PUBLIC.md +0 -0
  7. {validio_cli-5.2.0 → validio_cli-5.3.0}/validio_cli/bin/entities/channels.py +0 -0
  8. {validio_cli-5.2.0 → validio_cli-5.3.0}/validio_cli/bin/entities/code.py +0 -0
  9. {validio_cli-5.2.0 → validio_cli-5.3.0}/validio_cli/bin/entities/config.py +0 -0
  10. {validio_cli-5.2.0 → validio_cli-5.3.0}/validio_cli/bin/entities/credentials.py +0 -0
  11. {validio_cli-5.2.0 → validio_cli-5.3.0}/validio_cli/bin/entities/dbt.py +0 -0
  12. {validio_cli-5.2.0 → validio_cli-5.3.0}/validio_cli/bin/entities/filters.py +0 -0
  13. {validio_cli-5.2.0 → validio_cli-5.3.0}/validio_cli/bin/entities/incidents.py +0 -0
  14. {validio_cli-5.2.0 → validio_cli-5.3.0}/validio_cli/bin/entities/metrics.py +0 -0
  15. {validio_cli-5.2.0 → validio_cli-5.3.0}/validio_cli/bin/entities/namespaces.py +0 -0
  16. {validio_cli-5.2.0 → validio_cli-5.3.0}/validio_cli/bin/entities/notification_rules.py +0 -0
  17. {validio_cli-5.2.0 → validio_cli-5.3.0}/validio_cli/bin/entities/resources.py +0 -0
  18. {validio_cli-5.2.0 → validio_cli-5.3.0}/validio_cli/bin/entities/segmentations.py +0 -0
  19. {validio_cli-5.2.0 → validio_cli-5.3.0}/validio_cli/bin/entities/segments.py +0 -0
  20. {validio_cli-5.2.0 → validio_cli-5.3.0}/validio_cli/bin/entities/users.py +0 -0
  21. {validio_cli-5.2.0 → validio_cli-5.3.0}/validio_cli/bin/entities/validators.py +0 -0
  22. {validio_cli-5.2.0 → validio_cli-5.3.0}/validio_cli/bin/entities/windows.py +0 -0
  23. {validio_cli-5.2.0 → validio_cli-5.3.0}/validio_cli/bin/main.py +0 -0
  24. {validio_cli-5.2.0 → validio_cli-5.3.0}/validio_cli/components.py +0 -0
  25. {validio_cli-5.2.0 → validio_cli-5.3.0}/validio_cli/metadata.py +0 -0
  26. {validio_cli-5.2.0 → validio_cli-5.3.0}/validio_cli/namespace.py +0 -0
  27. {validio_cli-5.2.0 → validio_cli-5.3.0}/validio_cli/schema.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: validio-cli
3
- Version: 5.2.0
3
+ Version: 5.3.0
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 = "5.2.0"
6
+ version = "5.3.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"
@@ -138,7 +138,7 @@ def output_json(obj: Any, identifier: str | None = None) -> None:
138
138
  print(j)
139
139
 
140
140
 
141
- def output_text(items: Any, fields: dict[str, OutputSettings | None]) -> None:
141
+ def format_text(items: Any, fields: dict[str, OutputSettings | None]) -> str:
142
142
  if items is None:
143
143
  items = []
144
144
  elif not isinstance(items, list):
@@ -175,8 +175,11 @@ def output_text(items: Any, fields: dict[str, OutputSettings | None]) -> None:
175
175
 
176
176
  table.append(row)
177
177
 
178
- datatable = tabulate(table, tablefmt="plain")
179
- print(datatable)
178
+ return tabulate(table, tablefmt="plain")
179
+
180
+
181
+ def output_text(items: Any, fields: dict[str, OutputSettings | None]) -> None:
182
+ print(format_text(items=items, fields=fields))
180
183
 
181
184
  # We flush stdout here so we can avoid an unhandled broken pipe error.
182
185
  # Catching the exception according to the documentation at
@@ -2,7 +2,7 @@ import asyncio
2
2
  import json
3
3
  import uuid
4
4
  from pathlib import Path
5
- from typing import Any, cast
5
+ from typing import Any, Optional, cast
6
6
 
7
7
  import click
8
8
  import typer
@@ -30,12 +30,14 @@ from validio_cli import (
30
30
  OutputFormatOption,
31
31
  OutputSettings,
32
32
  components,
33
+ format_text,
33
34
  get_client,
34
35
  output_json,
35
36
  output_ok_or_error,
36
37
  output_text,
37
38
  )
38
39
  from validio_cli.bin.entities import credentials
40
+ from validio_cli.components import proceed_with_operation
39
41
  from validio_cli.namespace import get_namespace
40
42
 
41
43
  app = AsyncTyper(help="Data sources to ingest data from")
@@ -132,25 +134,96 @@ async def delete(
132
134
  return output_ok_or_error(result)
133
135
 
134
136
 
137
+ async def prompt_for_start_all_sources(
138
+ client: APIClient,
139
+ cfg: ValidioConfig,
140
+ namespace: str,
141
+ auto_approve: bool,
142
+ ) -> list[str] | None:
143
+ if not namespace:
144
+ raise typer.BadParameter("Namespace must be supplied when using --all")
145
+
146
+ sources = await client.get_sources(
147
+ namespace_id=get_namespace(namespace, cfg),
148
+ )
149
+
150
+ if not isinstance(sources, list):
151
+ raise ValidioError("failed to get sources")
152
+
153
+ if len(sources) == 0:
154
+ print("No sources exist in namespace to start")
155
+ return None
156
+
157
+ sources_to_start = [
158
+ source for source in sources if source.get("state") in ["IDLE", "INIT"]
159
+ ]
160
+
161
+ if len(sources_to_start) == 0:
162
+ print("No sources in namespace that need starting")
163
+ return None
164
+
165
+ prompt = "The following sources will be started:\n"
166
+ prompt += format_text(
167
+ sources_to_start,
168
+ fields={
169
+ "id": OutputSettings(attribute_name="id"),
170
+ "resource_name": OutputSettings(attribute_name="resourceName"),
171
+ "type": OutputSettings.trimmed_upper_snake(
172
+ attribute_name="__typename", trim="Source"
173
+ ),
174
+ "state": None,
175
+ },
176
+ )
177
+
178
+ print(prompt)
179
+
180
+ if not await proceed_with_operation(auto_approve):
181
+ return None
182
+ print()
183
+
184
+ return [source["id"] for source in sources_to_start]
185
+
186
+
135
187
  @app.async_command(help="Start sources")
136
188
  async def start(
137
189
  config_dir: str = ConfigDir,
138
190
  output_format: OutputFormat = OutputFormatOption,
139
191
  # ruff: noqa: ARG001
140
192
  namespace: str = Namespace(),
141
- identifiers: list[str] = Identifiers,
193
+ all: bool = typer.Option(False, "--all", help="Start all unstarted sources"),
194
+ auto_approve: bool = typer.Option(
195
+ False,
196
+ help="When used with --all, automatically approve starting of all sources",
197
+ ),
198
+ identifiers: Optional[list[str]] = Identifiers,
142
199
  ) -> None:
143
200
  client, cfg = get_client(config_dir)
144
- async with client.client as session:
145
- await apply_source_action_on_chunks(
146
- session,
147
- cfg,
148
- SourceAction.START,
149
- split_to_chunks(identifiers),
150
- namespace,
151
- output_format,
201
+
202
+ if all and identifiers and len(identifiers) > 0:
203
+ raise typer.BadParameter(
204
+ "Cannot use both --all and supplying source "
205
+ "identifiers. You must only use one of them"
152
206
  )
153
207
 
208
+ if all:
209
+ identifiers = await prompt_for_start_all_sources(
210
+ client=client,
211
+ cfg=cfg,
212
+ namespace=namespace,
213
+ auto_approve=auto_approve,
214
+ )
215
+
216
+ if identifiers:
217
+ async with client.client as session:
218
+ await apply_source_action_on_chunks(
219
+ session,
220
+ cfg,
221
+ SourceAction.START,
222
+ split_to_chunks(identifiers),
223
+ namespace,
224
+ output_format,
225
+ )
226
+
154
227
 
155
228
  @app.async_command(help="Stop source")
156
229
  async def stop(
@@ -1023,7 +1096,11 @@ async def apply_source_action_on_chunks(
1023
1096
  ]
1024
1097
  )
1025
1098
 
1026
- return handle_source_action_response(results, action, output_format)
1099
+ return await handle_source_action_response(
1100
+ results,
1101
+ action,
1102
+ output_format,
1103
+ )
1027
1104
 
1028
1105
 
1029
1106
  async def apply_batch_source_action(
@@ -1049,15 +1126,17 @@ async def apply_single_source_action(
1049
1126
  source_id = await _get_source_id(session, cfg, identifier, namespace)
1050
1127
  if source_id is None:
1051
1128
  return {
1052
- f"source{action.value}": {
1053
- "errors": [{"message": f"No source with id or name {identifier} found"}]
1054
- }
1129
+ "errors": [{"message": f"No source with id or name {identifier} found"}]
1055
1130
  }
1056
1131
 
1057
- return await apply_source_action(session, action, source_id)
1132
+ result = await apply_source_action(session, action, source_id)
1133
+ return {
1134
+ "id": source_id,
1135
+ **result,
1136
+ }
1058
1137
 
1059
1138
 
1060
- def handle_source_action_response(
1139
+ async def handle_source_action_response(
1061
1140
  results: list[list[dict[str, Any]]],
1062
1141
  action: SourceAction,
1063
1142
  output_format: OutputFormat,
@@ -1086,7 +1165,19 @@ def handle_source_action_response(
1086
1165
  if output_format == OutputFormat.JSON:
1087
1166
  return output_json(result)
1088
1167
 
1089
- return output_text(
1168
+ output_action_word = ""
1169
+ match action:
1170
+ case SourceAction.START:
1171
+ output_action_word = "starting"
1172
+ case SourceAction.STOP:
1173
+ output_action_word = "stopping"
1174
+ case SourceAction.BACKFILL:
1175
+ output_action_word = "initiating backfilling"
1176
+ case SourceAction.RESET:
1177
+ output_action_word = "resetting"
1178
+
1179
+ print(f"Summary of results when {output_action_word} sources:")
1180
+ output_text(
1090
1181
  result,
1091
1182
  fields={
1092
1183
  field_name: None,
@@ -1094,6 +1185,26 @@ def handle_source_action_response(
1094
1185
  },
1095
1186
  )
1096
1187
 
1188
+ if len(error_responses) > 0:
1189
+ print()
1190
+ print(f"Failures details when {output_action_word} sources:")
1191
+ output_text(
1192
+ error_responses,
1193
+ fields={
1194
+ "source": OutputSettings(
1195
+ attribute_name="id",
1196
+ ),
1197
+ "error": OutputSettings(
1198
+ attribute_name="errors",
1199
+ reformat=lambda errors: ",".join(
1200
+ [error["message"] for error in errors]
1201
+ ),
1202
+ ),
1203
+ },
1204
+ )
1205
+
1206
+ return None
1207
+
1097
1208
 
1098
1209
  if __name__ == "__main__":
1099
1210
  typer.run(app())
File without changes