pltr-cli 0.6.0__py3-none-any.whl → 0.8.0__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.
pltr/commands/dataset.py CHANGED
@@ -22,6 +22,9 @@ branches_app = typer.Typer()
22
22
  files_app = typer.Typer()
23
23
  transactions_app = typer.Typer()
24
24
  views_app = typer.Typer()
25
+ schema_app = typer.Typer()
26
+ schedules_app = typer.Typer()
27
+ jobs_app = typer.Typer()
25
28
  console = Console()
26
29
  formatter = OutputFormatter(console)
27
30
 
@@ -70,7 +73,175 @@ def get_dataset(
70
73
  raise typer.Exit(1)
71
74
 
72
75
 
73
- # schema command removed - uses preview-only API that returns INVALID_ARGUMENT
76
+ # Schema commands
77
+ @schema_app.command("get")
78
+ def get_schema(
79
+ dataset_rid: str = typer.Argument(
80
+ ..., help="Dataset Resource Identifier", autocompletion=complete_rid
81
+ ),
82
+ profile: Optional[str] = typer.Option(
83
+ None, "--profile", "-p", help="Profile name", autocompletion=complete_profile
84
+ ),
85
+ format: str = typer.Option(
86
+ "table",
87
+ "--format",
88
+ "-f",
89
+ help="Output format (table, json, csv)",
90
+ autocompletion=complete_output_format,
91
+ ),
92
+ output: Optional[str] = typer.Option(
93
+ None, "--output", "-o", help="Output file path"
94
+ ),
95
+ ):
96
+ """Get the schema of a dataset."""
97
+ try:
98
+ cache_rid(dataset_rid)
99
+ service = DatasetService(profile=profile)
100
+
101
+ with SpinnerProgressTracker().track_spinner(
102
+ f"Fetching schema for {dataset_rid}..."
103
+ ):
104
+ schema = service.get_schema(dataset_rid)
105
+
106
+ # Format schema for display
107
+ if format == "json":
108
+ formatter._format_json(schema, output)
109
+ else:
110
+ formatter.print_info(f"Dataset: {dataset_rid}")
111
+ formatter.print_info(f"Status: {schema.get('status', 'Unknown')}")
112
+ if schema.get("schema"):
113
+ formatter.print_info("\nSchema:")
114
+ formatter._format_json(schema.get("schema"))
115
+
116
+ if output:
117
+ formatter.print_success(f"Schema saved to {output}")
118
+
119
+ except (ProfileNotFoundError, MissingCredentialsError) as e:
120
+ formatter.print_error(f"Authentication error: {e}")
121
+ raise typer.Exit(1)
122
+ except Exception as e:
123
+ formatter.print_error(f"Failed to get schema: {e}")
124
+ raise typer.Exit(1)
125
+
126
+
127
+ @schema_app.command("set")
128
+ def set_schema(
129
+ dataset_rid: str = typer.Argument(
130
+ ..., help="Dataset Resource Identifier", autocompletion=complete_rid
131
+ ),
132
+ from_csv: Optional[str] = typer.Option(
133
+ None, "--from-csv", help="Infer schema from CSV file"
134
+ ),
135
+ json_schema: Optional[str] = typer.Option(
136
+ None, "--json", help="JSON string defining the schema"
137
+ ),
138
+ json_file: Optional[str] = typer.Option(
139
+ None, "--json-file", help="Path to JSON file containing schema definition"
140
+ ),
141
+ branch: str = typer.Option("master", "--branch", help="Dataset branch"),
142
+ transaction_rid: Optional[str] = typer.Option(
143
+ None, "--transaction-rid", help="Transaction RID to use"
144
+ ),
145
+ profile: Optional[str] = typer.Option(
146
+ None, "--profile", "-p", help="Profile name", autocompletion=complete_profile
147
+ ),
148
+ ):
149
+ """Set or update the schema of a dataset."""
150
+ try:
151
+ cache_rid(dataset_rid)
152
+ service = DatasetService(profile=profile)
153
+
154
+ # Validate that exactly one input method is provided
155
+ input_methods = [from_csv, json_schema, json_file]
156
+ if sum(x is not None for x in input_methods) != 1:
157
+ formatter.print_error(
158
+ "Exactly one of --from-csv, --json, or --json-file must be provided"
159
+ )
160
+ raise typer.Exit(1)
161
+
162
+ schema = None
163
+
164
+ # Infer schema from CSV
165
+ if from_csv:
166
+ with SpinnerProgressTracker().track_spinner(
167
+ f"Inferring schema from {from_csv}..."
168
+ ):
169
+ schema = service.infer_schema_from_csv(from_csv)
170
+ formatter.print_info(
171
+ f"Inferred schema from CSV with {len(schema.field_schema_list)} fields"
172
+ )
173
+ for field in schema.field_schema_list:
174
+ formatter.print_info(
175
+ f" - {field.name}: {field.type} (nullable={field.nullable})"
176
+ )
177
+
178
+ # Parse schema from JSON string
179
+ elif json_schema:
180
+ import json
181
+ from foundry_sdk.v2.core.models import DatasetSchema, DatasetFieldSchema
182
+
183
+ try:
184
+ schema_data = json.loads(json_schema)
185
+ fields = []
186
+ for field_data in schema_data.get("fields", []):
187
+ fields.append(
188
+ DatasetFieldSchema(
189
+ name=field_data["name"],
190
+ type=field_data["type"],
191
+ nullable=field_data.get("nullable", True),
192
+ )
193
+ )
194
+ schema = DatasetSchema(field_schema_list=fields)
195
+ except (json.JSONDecodeError, KeyError) as e:
196
+ formatter.print_error(f"Invalid JSON schema: {e}")
197
+ raise typer.Exit(1)
198
+
199
+ # Load schema from JSON file
200
+ elif json_file:
201
+ import json
202
+ from foundry_sdk.v2.core.models import DatasetSchema, DatasetFieldSchema
203
+
204
+ try:
205
+ with open(json_file, "r") as f:
206
+ schema_data = json.load(f)
207
+ fields = []
208
+ for field_data in schema_data.get("fields", []):
209
+ fields.append(
210
+ DatasetFieldSchema(
211
+ name=field_data["name"],
212
+ type=field_data["type"],
213
+ nullable=field_data.get("nullable", True),
214
+ )
215
+ )
216
+ schema = DatasetSchema(field_schema_list=fields)
217
+ except (FileNotFoundError, json.JSONDecodeError, KeyError) as e:
218
+ formatter.print_error(f"Failed to load schema from file: {e}")
219
+ raise typer.Exit(1)
220
+
221
+ # Apply the schema
222
+ with SpinnerProgressTracker().track_spinner(
223
+ f"Setting schema on dataset {dataset_rid}..."
224
+ ):
225
+ service.put_schema(
226
+ dataset_rid=dataset_rid,
227
+ schema=schema,
228
+ branch=branch,
229
+ transaction_rid=transaction_rid,
230
+ )
231
+
232
+ formatter.print_success(f"Successfully set schema on dataset {dataset_rid}")
233
+ if transaction_rid:
234
+ formatter.print_info(f"Transaction RID: {transaction_rid}")
235
+
236
+ except (ProfileNotFoundError, MissingCredentialsError) as e:
237
+ formatter.print_error(f"Authentication error: {e}")
238
+ raise typer.Exit(1)
239
+ except FileNotFoundError as e:
240
+ formatter.print_error(f"File not found: {e}")
241
+ raise typer.Exit(1)
242
+ except Exception as e:
243
+ formatter.print_error(f"Failed to set schema: {e}")
244
+ raise typer.Exit(1)
74
245
 
75
246
 
76
247
  @app.command("create")
@@ -189,6 +360,139 @@ def create_branch(
189
360
  raise typer.Exit(1)
190
361
 
191
362
 
363
+ @branches_app.command("delete")
364
+ def delete_branch(
365
+ dataset_rid: str = typer.Argument(
366
+ ..., help="Dataset Resource Identifier", autocompletion=complete_rid
367
+ ),
368
+ branch_name: str = typer.Argument(..., help="Branch name to delete"),
369
+ confirm: bool = typer.Option(False, "--yes", "-y", help="Skip confirmation prompt"),
370
+ profile: Optional[str] = typer.Option(
371
+ None, "--profile", "-p", help="Profile name", autocompletion=complete_profile
372
+ ),
373
+ ):
374
+ """Delete a branch from a dataset."""
375
+ try:
376
+ cache_rid(dataset_rid)
377
+ service = DatasetService(profile=profile)
378
+
379
+ # Prevent deleting master branch
380
+ if branch_name.lower() == "master":
381
+ formatter.print_error("Cannot delete the master branch")
382
+ raise typer.Exit(1)
383
+
384
+ # Confirmation prompt
385
+ if not confirm:
386
+ confirmed = typer.confirm(
387
+ f"Are you sure you want to delete branch '{branch_name}' from dataset {dataset_rid}? "
388
+ f"This action cannot be undone."
389
+ )
390
+ if not confirmed:
391
+ formatter.print_info("Branch deletion cancelled")
392
+ raise typer.Exit(0)
393
+
394
+ with SpinnerProgressTracker().track_spinner(
395
+ f"Deleting branch '{branch_name}' from {dataset_rid}..."
396
+ ):
397
+ service.delete_branch(dataset_rid, branch_name)
398
+
399
+ formatter.print_success(f"Branch '{branch_name}' deleted successfully")
400
+ formatter.print_info(f"Dataset: {dataset_rid}")
401
+
402
+ except (ProfileNotFoundError, MissingCredentialsError) as e:
403
+ formatter.print_error(f"Authentication error: {e}")
404
+ raise typer.Exit(1)
405
+ except Exception as e:
406
+ formatter.print_error(f"Failed to delete branch: {e}")
407
+ raise typer.Exit(1)
408
+
409
+
410
+ @branches_app.command("get")
411
+ def get_branch(
412
+ dataset_rid: str = typer.Argument(
413
+ ..., help="Dataset Resource Identifier", autocompletion=complete_rid
414
+ ),
415
+ branch_name: str = typer.Argument(..., help="Branch name"),
416
+ profile: Optional[str] = typer.Option(
417
+ None, "--profile", "-p", help="Profile name", autocompletion=complete_profile
418
+ ),
419
+ format: str = typer.Option(
420
+ "table",
421
+ "--format",
422
+ "-f",
423
+ help="Output format (table, json, csv)",
424
+ autocompletion=complete_output_format,
425
+ ),
426
+ output: Optional[str] = typer.Option(
427
+ None, "--output", "-o", help="Output file path"
428
+ ),
429
+ ):
430
+ """Get detailed information about a specific branch."""
431
+ try:
432
+ cache_rid(dataset_rid)
433
+ service = DatasetService(profile=profile)
434
+
435
+ with SpinnerProgressTracker().track_spinner(
436
+ f"Fetching branch '{branch_name}' from {dataset_rid}..."
437
+ ):
438
+ branch = service.get_branch(dataset_rid, branch_name)
439
+
440
+ formatter.format_branch_detail(branch, format, output)
441
+
442
+ if output:
443
+ formatter.print_success(f"Branch information saved to {output}")
444
+
445
+ except (ProfileNotFoundError, MissingCredentialsError) as e:
446
+ formatter.print_error(f"Authentication error: {e}")
447
+ raise typer.Exit(1)
448
+ except Exception as e:
449
+ formatter.print_error(f"Failed to get branch: {e}")
450
+ raise typer.Exit(1)
451
+
452
+
453
+ @branches_app.command("transactions")
454
+ def list_branch_transactions(
455
+ dataset_rid: str = typer.Argument(
456
+ ..., help="Dataset Resource Identifier", autocompletion=complete_rid
457
+ ),
458
+ branch_name: str = typer.Argument(..., help="Branch name"),
459
+ profile: Optional[str] = typer.Option(
460
+ None, "--profile", "-p", help="Profile name", autocompletion=complete_profile
461
+ ),
462
+ format: str = typer.Option(
463
+ "table",
464
+ "--format",
465
+ "-f",
466
+ help="Output format (table, json, csv)",
467
+ autocompletion=complete_output_format,
468
+ ),
469
+ output: Optional[str] = typer.Option(
470
+ None, "--output", "-o", help="Output file path"
471
+ ),
472
+ ):
473
+ """Get transaction history for a specific branch."""
474
+ try:
475
+ cache_rid(dataset_rid)
476
+ service = DatasetService(profile=profile)
477
+
478
+ with SpinnerProgressTracker().track_spinner(
479
+ f"Fetching transaction history for branch '{branch_name}' in {dataset_rid}..."
480
+ ):
481
+ transactions = service.get_branch_transactions(dataset_rid, branch_name)
482
+
483
+ formatter.format_transactions(transactions, format, output)
484
+
485
+ if output:
486
+ formatter.print_success(f"Branch transactions saved to {output}")
487
+
488
+ except (ProfileNotFoundError, MissingCredentialsError) as e:
489
+ formatter.print_error(f"Authentication error: {e}")
490
+ raise typer.Exit(1)
491
+ except Exception as e:
492
+ formatter.print_error(f"Failed to get branch transactions: {e}")
493
+ raise typer.Exit(1)
494
+
495
+
192
496
  # Files commands
193
497
  @files_app.command("list")
194
498
  def list_files(
@@ -281,9 +585,30 @@ def upload_file(
281
585
 
282
586
  except (ProfileNotFoundError, MissingCredentialsError) as e:
283
587
  formatter.print_error(f"Authentication error: {e}")
588
+ raise typer.Exit(1)
589
+ except FileNotFoundError as e:
590
+ formatter.print_error(f"File error: {e}")
591
+ raise typer.Exit(1)
592
+ except RuntimeError as e:
593
+ # RuntimeError from our service layer contains detailed error info
594
+ error_msg = str(e)
595
+ formatter.print_error(f"Upload failed: {error_msg}")
596
+
597
+ # If it looks like our enhanced error message, extract the suggestion part
598
+ if ". Suggestions: " in error_msg:
599
+ main_error, suggestions = error_msg.split(". Suggestions: ", 1)
600
+ formatter.print_error(main_error)
601
+ formatter.print_info(f"💡 Suggestions: {suggestions}")
602
+
284
603
  raise typer.Exit(1)
285
604
  except Exception as e:
286
- formatter.print_error(f"Failed to upload file: {e}")
605
+ # Fallback for any other exceptions
606
+ formatter.print_error(
607
+ f"Unexpected error during file upload: {type(e).__name__}: {e}"
608
+ )
609
+ formatter.print_info(
610
+ "💡 Try running the command again or check your connection"
611
+ )
287
612
  raise typer.Exit(1)
288
613
 
289
614
 
@@ -320,6 +645,93 @@ def get_file(
320
645
  raise typer.Exit(1)
321
646
 
322
647
 
648
+ @files_app.command("delete")
649
+ def delete_file(
650
+ dataset_rid: str = typer.Argument(
651
+ ..., help="Dataset Resource Identifier", autocompletion=complete_rid
652
+ ),
653
+ file_path: str = typer.Argument(..., help="Path of file within dataset to delete"),
654
+ branch: str = typer.Option("master", "--branch", help="Dataset branch"),
655
+ confirm: bool = typer.Option(False, "--yes", "-y", help="Skip confirmation prompt"),
656
+ profile: Optional[str] = typer.Option(
657
+ None, "--profile", "-p", help="Profile name", autocompletion=complete_profile
658
+ ),
659
+ ):
660
+ """Delete a file from a dataset."""
661
+ try:
662
+ cache_rid(dataset_rid)
663
+ service = DatasetService(profile=profile)
664
+
665
+ # Confirmation prompt
666
+ if not confirm:
667
+ confirmed = typer.confirm(
668
+ f"Are you sure you want to delete '{file_path}' from dataset {dataset_rid}?"
669
+ )
670
+ if not confirmed:
671
+ formatter.print_info("File deletion cancelled")
672
+ raise typer.Exit(0)
673
+
674
+ with SpinnerProgressTracker().track_spinner(
675
+ f"Deleting {file_path} from {dataset_rid}..."
676
+ ):
677
+ service.delete_file(dataset_rid, file_path, branch)
678
+
679
+ formatter.print_success(f"File '{file_path}' deleted successfully")
680
+ formatter.print_info(f"Dataset: {dataset_rid}")
681
+ formatter.print_info(f"Branch: {branch}")
682
+
683
+ except (ProfileNotFoundError, MissingCredentialsError) as e:
684
+ formatter.print_error(f"Authentication error: {e}")
685
+ raise typer.Exit(1)
686
+ except Exception as e:
687
+ formatter.print_error(f"Failed to delete file: {e}")
688
+ raise typer.Exit(1)
689
+
690
+
691
+ @files_app.command("info")
692
+ def get_file_info(
693
+ dataset_rid: str = typer.Argument(
694
+ ..., help="Dataset Resource Identifier", autocompletion=complete_rid
695
+ ),
696
+ file_path: str = typer.Argument(..., help="Path of file within dataset"),
697
+ branch: str = typer.Option("master", "--branch", help="Dataset branch"),
698
+ profile: Optional[str] = typer.Option(
699
+ None, "--profile", "-p", help="Profile name", autocompletion=complete_profile
700
+ ),
701
+ format: str = typer.Option(
702
+ "table",
703
+ "--format",
704
+ "-f",
705
+ help="Output format (table, json, csv)",
706
+ autocompletion=complete_output_format,
707
+ ),
708
+ output: Optional[str] = typer.Option(
709
+ None, "--output", "-o", help="Output file path"
710
+ ),
711
+ ):
712
+ """Get metadata information about a file in a dataset."""
713
+ try:
714
+ cache_rid(dataset_rid)
715
+ service = DatasetService(profile=profile)
716
+
717
+ with SpinnerProgressTracker().track_spinner(
718
+ f"Getting file info for {file_path} in {dataset_rid}..."
719
+ ):
720
+ file_info = service.get_file_info(dataset_rid, file_path, branch)
721
+
722
+ formatter.format_file_info(file_info, format, output)
723
+
724
+ if output:
725
+ formatter.print_success(f"File information saved to {output}")
726
+
727
+ except (ProfileNotFoundError, MissingCredentialsError) as e:
728
+ formatter.print_error(f"Authentication error: {e}")
729
+ raise typer.Exit(1)
730
+ except Exception as e:
731
+ formatter.print_error(f"Failed to get file info: {e}")
732
+ raise typer.Exit(1)
733
+
734
+
323
735
  # Transaction commands
324
736
  @transactions_app.command("start")
325
737
  def start_transaction(
@@ -581,6 +993,49 @@ def list_transactions(
581
993
  raise typer.Exit(1)
582
994
 
583
995
 
996
+ @transactions_app.command("build")
997
+ def get_transaction_build(
998
+ dataset_rid: str = typer.Argument(
999
+ ..., help="Dataset Resource Identifier", autocompletion=complete_rid
1000
+ ),
1001
+ transaction_rid: str = typer.Argument(..., help="Transaction Resource Identifier"),
1002
+ profile: Optional[str] = typer.Option(
1003
+ None, "--profile", "-p", help="Profile name", autocompletion=complete_profile
1004
+ ),
1005
+ format: str = typer.Option(
1006
+ "table",
1007
+ "--format",
1008
+ "-f",
1009
+ help="Output format (table, json, csv)",
1010
+ autocompletion=complete_output_format,
1011
+ ),
1012
+ output: Optional[str] = typer.Option(
1013
+ None, "--output", "-o", help="Output file path"
1014
+ ),
1015
+ ):
1016
+ """Get build information for a transaction."""
1017
+ try:
1018
+ cache_rid(dataset_rid)
1019
+ service = DatasetService(profile=profile)
1020
+
1021
+ with SpinnerProgressTracker().track_spinner(
1022
+ f"Fetching build information for transaction {transaction_rid}..."
1023
+ ):
1024
+ build_info = service.get_transaction_build(dataset_rid, transaction_rid)
1025
+
1026
+ formatter.format_transaction_build(build_info, format, output)
1027
+
1028
+ if output:
1029
+ formatter.print_success(f"Build information saved to {output}")
1030
+
1031
+ except (ProfileNotFoundError, MissingCredentialsError) as e:
1032
+ formatter.print_error(f"Authentication error: {e}")
1033
+ raise typer.Exit(1)
1034
+ except Exception as e:
1035
+ formatter.print_error(f"Failed to get transaction build: {e}")
1036
+ raise typer.Exit(1)
1037
+
1038
+
584
1039
  # Views commands
585
1040
  @views_app.command("list")
586
1041
  def list_views(
@@ -627,6 +1082,223 @@ def list_views(
627
1082
  raise typer.Exit(1)
628
1083
 
629
1084
 
1085
+ @views_app.command("get")
1086
+ def get_view(
1087
+ view_rid: str = typer.Argument(
1088
+ ..., help="View Resource Identifier", autocompletion=complete_rid
1089
+ ),
1090
+ branch: str = typer.Option("master", "--branch", help="Branch name"),
1091
+ profile: Optional[str] = typer.Option(
1092
+ None, "--profile", "-p", help="Profile name", autocompletion=complete_profile
1093
+ ),
1094
+ format: str = typer.Option(
1095
+ "table",
1096
+ "--format",
1097
+ "-f",
1098
+ help="Output format (table, json, csv)",
1099
+ autocompletion=complete_output_format,
1100
+ ),
1101
+ output: Optional[str] = typer.Option(
1102
+ None, "--output", "-o", help="Output file path"
1103
+ ),
1104
+ ):
1105
+ """Get detailed information about a view."""
1106
+ try:
1107
+ cache_rid(view_rid)
1108
+ service = DatasetService(profile=profile)
1109
+
1110
+ with SpinnerProgressTracker().track_spinner(f"Fetching view {view_rid}..."):
1111
+ view = service.get_view(view_rid, branch)
1112
+
1113
+ formatter.format_view_detail(view, format, output)
1114
+
1115
+ if output:
1116
+ formatter.print_success(f"View information saved to {output}")
1117
+
1118
+ except (ProfileNotFoundError, MissingCredentialsError) as e:
1119
+ formatter.print_error(f"Authentication error: {e}")
1120
+ raise typer.Exit(1)
1121
+ except Exception as e:
1122
+ formatter.print_error(f"Failed to get view: {e}")
1123
+ raise typer.Exit(1)
1124
+
1125
+
1126
+ @views_app.command("add-datasets")
1127
+ def add_backing_datasets(
1128
+ view_rid: str = typer.Argument(
1129
+ ..., help="View Resource Identifier", autocompletion=complete_rid
1130
+ ),
1131
+ dataset_rids: list[str] = typer.Argument(
1132
+ ..., help="Dataset RIDs to add as backing datasets"
1133
+ ),
1134
+ profile: Optional[str] = typer.Option(
1135
+ None, "--profile", "-p", help="Profile name", autocompletion=complete_profile
1136
+ ),
1137
+ format: str = typer.Option(
1138
+ "table",
1139
+ "--format",
1140
+ "-f",
1141
+ help="Output format (table, json, csv)",
1142
+ autocompletion=complete_output_format,
1143
+ ),
1144
+ ):
1145
+ """Add backing datasets to a view."""
1146
+ try:
1147
+ cache_rid(view_rid)
1148
+ service = DatasetService(profile=profile)
1149
+
1150
+ with SpinnerProgressTracker().track_spinner(
1151
+ f"Adding {len(dataset_rids)} backing datasets to view {view_rid}..."
1152
+ ):
1153
+ result = service.add_backing_datasets(view_rid, dataset_rids)
1154
+
1155
+ formatter.print_success("Successfully added backing datasets to view")
1156
+ formatter.print_info(f"View RID: {view_rid}")
1157
+ formatter.print_info(f"Added datasets: {', '.join(dataset_rids)}")
1158
+
1159
+ if format == "json":
1160
+ formatter._format_json(result)
1161
+
1162
+ except (ProfileNotFoundError, MissingCredentialsError) as e:
1163
+ formatter.print_error(f"Authentication error: {e}")
1164
+ raise typer.Exit(1)
1165
+ except Exception as e:
1166
+ formatter.print_error(f"Failed to add backing datasets: {e}")
1167
+ raise typer.Exit(1)
1168
+
1169
+
1170
+ @views_app.command("remove-datasets")
1171
+ def remove_backing_datasets(
1172
+ view_rid: str = typer.Argument(
1173
+ ..., help="View Resource Identifier", autocompletion=complete_rid
1174
+ ),
1175
+ dataset_rids: list[str] = typer.Argument(
1176
+ ..., help="Dataset RIDs to remove as backing datasets"
1177
+ ),
1178
+ profile: Optional[str] = typer.Option(
1179
+ None, "--profile", "-p", help="Profile name", autocompletion=complete_profile
1180
+ ),
1181
+ format: str = typer.Option(
1182
+ "table",
1183
+ "--format",
1184
+ "-f",
1185
+ help="Output format (table, json, csv)",
1186
+ autocompletion=complete_output_format,
1187
+ ),
1188
+ ):
1189
+ """Remove backing datasets from a view."""
1190
+ try:
1191
+ cache_rid(view_rid)
1192
+ service = DatasetService(profile=profile)
1193
+
1194
+ with SpinnerProgressTracker().track_spinner(
1195
+ f"Removing {len(dataset_rids)} backing datasets from view {view_rid}..."
1196
+ ):
1197
+ result = service.remove_backing_datasets(view_rid, dataset_rids)
1198
+
1199
+ formatter.print_success("Successfully removed backing datasets from view")
1200
+ formatter.print_info(f"View RID: {view_rid}")
1201
+ formatter.print_info(f"Removed datasets: {', '.join(dataset_rids)}")
1202
+
1203
+ if format == "json":
1204
+ formatter._format_json(result)
1205
+
1206
+ except (ProfileNotFoundError, MissingCredentialsError) as e:
1207
+ formatter.print_error(f"Authentication error: {e}")
1208
+ raise typer.Exit(1)
1209
+ except Exception as e:
1210
+ formatter.print_error(f"Failed to remove backing datasets: {e}")
1211
+ raise typer.Exit(1)
1212
+
1213
+
1214
+ @views_app.command("replace-datasets")
1215
+ def replace_backing_datasets(
1216
+ view_rid: str = typer.Argument(
1217
+ ..., help="View Resource Identifier", autocompletion=complete_rid
1218
+ ),
1219
+ dataset_rids: list[str] = typer.Argument(
1220
+ ..., help="Dataset RIDs to set as backing datasets"
1221
+ ),
1222
+ profile: Optional[str] = typer.Option(
1223
+ None, "--profile", "-p", help="Profile name", autocompletion=complete_profile
1224
+ ),
1225
+ format: str = typer.Option(
1226
+ "table",
1227
+ "--format",
1228
+ "-f",
1229
+ help="Output format (table, json, csv)",
1230
+ autocompletion=complete_output_format,
1231
+ ),
1232
+ ):
1233
+ """Replace all backing datasets in a view."""
1234
+ try:
1235
+ cache_rid(view_rid)
1236
+ service = DatasetService(profile=profile)
1237
+
1238
+ with SpinnerProgressTracker().track_spinner(
1239
+ f"Replacing backing datasets in view {view_rid} with {len(dataset_rids)} new datasets..."
1240
+ ):
1241
+ result = service.replace_backing_datasets(view_rid, dataset_rids)
1242
+
1243
+ formatter.print_success("Successfully replaced backing datasets in view")
1244
+ formatter.print_info(f"View RID: {view_rid}")
1245
+ formatter.print_info(f"New datasets: {', '.join(dataset_rids)}")
1246
+
1247
+ if format == "json":
1248
+ formatter._format_json(result)
1249
+
1250
+ except (ProfileNotFoundError, MissingCredentialsError) as e:
1251
+ formatter.print_error(f"Authentication error: {e}")
1252
+ raise typer.Exit(1)
1253
+ except Exception as e:
1254
+ formatter.print_error(f"Failed to replace backing datasets: {e}")
1255
+ raise typer.Exit(1)
1256
+
1257
+
1258
+ @views_app.command("add-primary-key")
1259
+ def add_primary_key(
1260
+ view_rid: str = typer.Argument(
1261
+ ..., help="View Resource Identifier", autocompletion=complete_rid
1262
+ ),
1263
+ key_fields: list[str] = typer.Argument(
1264
+ ..., help="Field names to use as primary key"
1265
+ ),
1266
+ profile: Optional[str] = typer.Option(
1267
+ None, "--profile", "-p", help="Profile name", autocompletion=complete_profile
1268
+ ),
1269
+ format: str = typer.Option(
1270
+ "table",
1271
+ "--format",
1272
+ "-f",
1273
+ help="Output format (table, json, csv)",
1274
+ autocompletion=complete_output_format,
1275
+ ),
1276
+ ):
1277
+ """Add a primary key to a view."""
1278
+ try:
1279
+ cache_rid(view_rid)
1280
+ service = DatasetService(profile=profile)
1281
+
1282
+ with SpinnerProgressTracker().track_spinner(
1283
+ f"Adding primary key to view {view_rid}..."
1284
+ ):
1285
+ result = service.add_primary_key(view_rid, key_fields)
1286
+
1287
+ formatter.print_success("Successfully added primary key to view")
1288
+ formatter.print_info(f"View RID: {view_rid}")
1289
+ formatter.print_info(f"Primary key fields: {', '.join(key_fields)}")
1290
+
1291
+ if format == "json":
1292
+ formatter._format_json(result)
1293
+
1294
+ except (ProfileNotFoundError, MissingCredentialsError) as e:
1295
+ formatter.print_error(f"Authentication error: {e}")
1296
+ raise typer.Exit(1)
1297
+ except Exception as e:
1298
+ formatter.print_error(f"Failed to add primary key: {e}")
1299
+ raise typer.Exit(1)
1300
+
1301
+
630
1302
  @views_app.command("create")
631
1303
  def create_view(
632
1304
  dataset_rid: str = typer.Argument(
@@ -671,11 +1343,101 @@ def create_view(
671
1343
  raise typer.Exit(1)
672
1344
 
673
1345
 
1346
+ # Schedules commands
1347
+ @schedules_app.command("list")
1348
+ def list_schedules(
1349
+ dataset_rid: str = typer.Argument(
1350
+ ..., help="Dataset Resource Identifier", autocompletion=complete_rid
1351
+ ),
1352
+ profile: Optional[str] = typer.Option(
1353
+ None, "--profile", "-p", help="Profile name", autocompletion=complete_profile
1354
+ ),
1355
+ format: str = typer.Option(
1356
+ "table",
1357
+ "--format",
1358
+ "-f",
1359
+ help="Output format (table, json, csv)",
1360
+ autocompletion=complete_output_format,
1361
+ ),
1362
+ output: Optional[str] = typer.Option(
1363
+ None, "--output", "-o", help="Output file path"
1364
+ ),
1365
+ ):
1366
+ """List schedules that target a specific dataset."""
1367
+ try:
1368
+ cache_rid(dataset_rid)
1369
+ service = DatasetService(profile=profile)
1370
+
1371
+ with SpinnerProgressTracker().track_spinner(
1372
+ f"Fetching schedules for dataset {dataset_rid}..."
1373
+ ):
1374
+ schedules = service.get_schedules(dataset_rid)
1375
+
1376
+ formatter.format_schedules(schedules, format, output)
1377
+
1378
+ if output:
1379
+ formatter.print_success(f"Schedules information saved to {output}")
1380
+
1381
+ except (ProfileNotFoundError, MissingCredentialsError) as e:
1382
+ formatter.print_error(f"Authentication error: {e}")
1383
+ raise typer.Exit(1)
1384
+ except Exception as e:
1385
+ formatter.print_error(f"Failed to get schedules: {e}")
1386
+ raise typer.Exit(1)
1387
+
1388
+
1389
+ # Jobs commands
1390
+ @jobs_app.command("list")
1391
+ def list_jobs(
1392
+ dataset_rid: str = typer.Argument(
1393
+ ..., help="Dataset Resource Identifier", autocompletion=complete_rid
1394
+ ),
1395
+ branch: str = typer.Option("master", "--branch", help="Dataset branch"),
1396
+ profile: Optional[str] = typer.Option(
1397
+ None, "--profile", "-p", help="Profile name", autocompletion=complete_profile
1398
+ ),
1399
+ format: str = typer.Option(
1400
+ "table",
1401
+ "--format",
1402
+ "-f",
1403
+ help="Output format (table, json, csv)",
1404
+ autocompletion=complete_output_format,
1405
+ ),
1406
+ output: Optional[str] = typer.Option(
1407
+ None, "--output", "-o", help="Output file path"
1408
+ ),
1409
+ ):
1410
+ """List jobs for a specific dataset."""
1411
+ try:
1412
+ cache_rid(dataset_rid)
1413
+ service = DatasetService(profile=profile)
1414
+
1415
+ with SpinnerProgressTracker().track_spinner(
1416
+ f"Fetching jobs for dataset {dataset_rid} (branch: {branch})..."
1417
+ ):
1418
+ jobs = service.get_jobs(dataset_rid, branch)
1419
+
1420
+ formatter.format_jobs(jobs, format, output)
1421
+
1422
+ if output:
1423
+ formatter.print_success(f"Jobs information saved to {output}")
1424
+
1425
+ except (ProfileNotFoundError, MissingCredentialsError) as e:
1426
+ formatter.print_error(f"Authentication error: {e}")
1427
+ raise typer.Exit(1)
1428
+ except Exception as e:
1429
+ formatter.print_error(f"Failed to get jobs: {e}")
1430
+ raise typer.Exit(1)
1431
+
1432
+
674
1433
  # Add subcommands to main app
675
1434
  app.add_typer(branches_app, name="branches")
676
1435
  app.add_typer(files_app, name="files")
677
1436
  app.add_typer(transactions_app, name="transactions")
678
1437
  app.add_typer(views_app, name="views")
1438
+ app.add_typer(schema_app, name="schema")
1439
+ app.add_typer(schedules_app, name="schedules")
1440
+ app.add_typer(jobs_app, name="jobs")
679
1441
 
680
1442
 
681
1443
  @app.callback()