pltr-cli 0.11.0__py3-none-any.whl → 0.12.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/resource.py CHANGED
@@ -459,6 +459,378 @@ def move_resource(
459
459
  raise typer.Exit(1)
460
460
 
461
461
 
462
+ # ==================== Trash Operations ====================
463
+
464
+
465
+ @app.command("delete")
466
+ def delete_resource(
467
+ resource_rid: str = typer.Argument(
468
+ ..., help="Resource Identifier", autocompletion=complete_rid
469
+ ),
470
+ profile: Optional[str] = typer.Option(
471
+ None, "--profile", help="Profile name", autocompletion=complete_profile
472
+ ),
473
+ force: bool = typer.Option(False, "--force", "-f", help="Skip confirmation prompt"),
474
+ ):
475
+ """Move a resource to trash."""
476
+ try:
477
+ if not force:
478
+ confirm = typer.confirm(
479
+ f"Are you sure you want to move resource {resource_rid} to trash?"
480
+ )
481
+ if not confirm:
482
+ formatter.print_info("Operation cancelled.")
483
+ return
484
+
485
+ service = ResourceService(profile=profile)
486
+
487
+ with SpinnerProgressTracker().track_spinner(
488
+ f"Moving resource {resource_rid} to trash..."
489
+ ):
490
+ service.delete_resource(resource_rid)
491
+
492
+ formatter.print_success(f"Successfully moved resource {resource_rid} to trash")
493
+
494
+ except (ProfileNotFoundError, MissingCredentialsError) as e:
495
+ formatter.print_error(f"Authentication error: {e}")
496
+ raise typer.Exit(1)
497
+ except Exception as e:
498
+ formatter.print_error(f"Failed to delete resource: {e}")
499
+ raise typer.Exit(1)
500
+
501
+
502
+ @app.command("restore")
503
+ def restore_resource(
504
+ resource_rid: str = typer.Argument(
505
+ ..., help="Resource Identifier", autocompletion=complete_rid
506
+ ),
507
+ profile: Optional[str] = typer.Option(
508
+ None, "--profile", help="Profile name", autocompletion=complete_profile
509
+ ),
510
+ ):
511
+ """Restore a resource from trash."""
512
+ try:
513
+ service = ResourceService(profile=profile)
514
+
515
+ with SpinnerProgressTracker().track_spinner(
516
+ f"Restoring resource {resource_rid} from trash..."
517
+ ):
518
+ service.restore_resource(resource_rid)
519
+
520
+ formatter.print_success(
521
+ f"Successfully restored resource {resource_rid} from trash"
522
+ )
523
+
524
+ except (ProfileNotFoundError, MissingCredentialsError) as e:
525
+ formatter.print_error(f"Authentication error: {e}")
526
+ raise typer.Exit(1)
527
+ except Exception as e:
528
+ formatter.print_error(f"Failed to restore resource: {e}")
529
+ raise typer.Exit(1)
530
+
531
+
532
+ @app.command("permanently-delete")
533
+ def permanently_delete_resource(
534
+ resource_rid: str = typer.Argument(
535
+ ..., help="Resource Identifier", autocompletion=complete_rid
536
+ ),
537
+ profile: Optional[str] = typer.Option(
538
+ None, "--profile", help="Profile name", autocompletion=complete_profile
539
+ ),
540
+ force: bool = typer.Option(False, "--force", "-f", help="Skip confirmation prompt"),
541
+ ):
542
+ """Permanently delete a resource from trash. This action is irreversible."""
543
+ try:
544
+ if not force:
545
+ confirm = typer.confirm(
546
+ f"Are you sure you want to PERMANENTLY delete resource {resource_rid}? "
547
+ "This action cannot be undone!"
548
+ )
549
+ if not confirm:
550
+ formatter.print_info("Operation cancelled.")
551
+ return
552
+
553
+ service = ResourceService(profile=profile)
554
+
555
+ with SpinnerProgressTracker().track_spinner(
556
+ f"Permanently deleting resource {resource_rid}..."
557
+ ):
558
+ service.permanently_delete_resource(resource_rid)
559
+
560
+ formatter.print_success(
561
+ f"Successfully permanently deleted resource {resource_rid}"
562
+ )
563
+
564
+ except (ProfileNotFoundError, MissingCredentialsError) as e:
565
+ formatter.print_error(f"Authentication error: {e}")
566
+ raise typer.Exit(1)
567
+ except Exception as e:
568
+ formatter.print_error(f"Failed to permanently delete resource: {e}")
569
+ raise typer.Exit(1)
570
+
571
+
572
+ # ==================== Markings Operations ====================
573
+
574
+
575
+ @app.command("add-markings")
576
+ def add_markings(
577
+ resource_rid: str = typer.Argument(
578
+ ..., help="Resource Identifier", autocompletion=complete_rid
579
+ ),
580
+ marking_ids: List[str] = typer.Option(
581
+ ...,
582
+ "--marking",
583
+ "-m",
584
+ help="Marking identifier(s) to add (can specify multiple)",
585
+ ),
586
+ profile: Optional[str] = typer.Option(
587
+ None, "--profile", help="Profile name", autocompletion=complete_profile
588
+ ),
589
+ ):
590
+ """Add markings to a resource."""
591
+ try:
592
+ service = ResourceService(profile=profile)
593
+
594
+ with SpinnerProgressTracker().track_spinner(
595
+ f"Adding {len(marking_ids)} marking(s) to resource {resource_rid}..."
596
+ ):
597
+ service.add_markings(resource_rid, marking_ids)
598
+
599
+ formatter.print_success(
600
+ f"Successfully added {len(marking_ids)} marking(s) to resource {resource_rid}"
601
+ )
602
+
603
+ except (ProfileNotFoundError, MissingCredentialsError) as e:
604
+ formatter.print_error(f"Authentication error: {e}")
605
+ raise typer.Exit(1)
606
+ except Exception as e:
607
+ formatter.print_error(f"Failed to add markings: {e}")
608
+ raise typer.Exit(1)
609
+
610
+
611
+ @app.command("remove-markings")
612
+ def remove_markings(
613
+ resource_rid: str = typer.Argument(
614
+ ..., help="Resource Identifier", autocompletion=complete_rid
615
+ ),
616
+ marking_ids: List[str] = typer.Option(
617
+ ...,
618
+ "--marking",
619
+ "-m",
620
+ help="Marking identifier(s) to remove (can specify multiple)",
621
+ ),
622
+ profile: Optional[str] = typer.Option(
623
+ None, "--profile", help="Profile name", autocompletion=complete_profile
624
+ ),
625
+ ):
626
+ """Remove markings from a resource."""
627
+ try:
628
+ service = ResourceService(profile=profile)
629
+
630
+ with SpinnerProgressTracker().track_spinner(
631
+ f"Removing {len(marking_ids)} marking(s) from resource {resource_rid}..."
632
+ ):
633
+ service.remove_markings(resource_rid, marking_ids)
634
+
635
+ formatter.print_success(
636
+ f"Successfully removed {len(marking_ids)} marking(s) from resource {resource_rid}"
637
+ )
638
+
639
+ except (ProfileNotFoundError, MissingCredentialsError) as e:
640
+ formatter.print_error(f"Authentication error: {e}")
641
+ raise typer.Exit(1)
642
+ except Exception as e:
643
+ formatter.print_error(f"Failed to remove markings: {e}")
644
+ raise typer.Exit(1)
645
+
646
+
647
+ @app.command("list-markings")
648
+ def list_markings(
649
+ resource_rid: str = typer.Argument(
650
+ ..., help="Resource Identifier", autocompletion=complete_rid
651
+ ),
652
+ profile: Optional[str] = typer.Option(
653
+ None, "--profile", help="Profile name", autocompletion=complete_profile
654
+ ),
655
+ format: str = typer.Option(
656
+ "table",
657
+ "--format",
658
+ "-f",
659
+ help="Output format (table, json, csv)",
660
+ autocompletion=complete_output_format,
661
+ ),
662
+ output: Optional[str] = typer.Option(
663
+ None, "--output", "-o", help="Output file path"
664
+ ),
665
+ page_size: Optional[int] = typer.Option(
666
+ None, "--page-size", help="Number of items per page"
667
+ ),
668
+ ):
669
+ """List markings directly applied to a resource."""
670
+ try:
671
+ service = ResourceService(profile=profile)
672
+
673
+ with SpinnerProgressTracker().track_spinner(
674
+ f"Fetching markings for resource {resource_rid}..."
675
+ ):
676
+ markings = service.list_markings(resource_rid, page_size=page_size)
677
+
678
+ if not markings:
679
+ formatter.print_info("No markings found on this resource.")
680
+ return
681
+
682
+ # Format output
683
+ if format == "json":
684
+ if output:
685
+ formatter.save_to_file(markings, output, "json")
686
+ else:
687
+ formatter.format_list(markings)
688
+ elif format == "csv":
689
+ if output:
690
+ formatter.save_to_file(markings, output, "csv")
691
+ else:
692
+ formatter.format_list(markings)
693
+ else:
694
+ _format_markings_table(markings)
695
+
696
+ if output:
697
+ formatter.print_success(f"Markings list saved to {output}")
698
+
699
+ except (ProfileNotFoundError, MissingCredentialsError) as e:
700
+ formatter.print_error(f"Authentication error: {e}")
701
+ raise typer.Exit(1)
702
+ except Exception as e:
703
+ formatter.print_error(f"Failed to list markings: {e}")
704
+ raise typer.Exit(1)
705
+
706
+
707
+ # ==================== Access & Batch Operations ====================
708
+
709
+
710
+ @app.command("access-requirements")
711
+ def get_access_requirements(
712
+ resource_rid: str = typer.Argument(
713
+ ..., help="Resource Identifier", autocompletion=complete_rid
714
+ ),
715
+ profile: Optional[str] = typer.Option(
716
+ None, "--profile", help="Profile name", autocompletion=complete_profile
717
+ ),
718
+ format: str = typer.Option(
719
+ "table",
720
+ "--format",
721
+ "-f",
722
+ help="Output format (table, json, csv)",
723
+ autocompletion=complete_output_format,
724
+ ),
725
+ output: Optional[str] = typer.Option(
726
+ None, "--output", "-o", help="Output file path"
727
+ ),
728
+ ):
729
+ """Get access requirements (organizations and markings) for a resource."""
730
+ try:
731
+ service = ResourceService(profile=profile)
732
+
733
+ with SpinnerProgressTracker().track_spinner(
734
+ f"Fetching access requirements for resource {resource_rid}..."
735
+ ):
736
+ requirements = service.get_access_requirements(resource_rid)
737
+
738
+ # Format output
739
+ if format == "json":
740
+ if output:
741
+ formatter.save_to_file(requirements, output, "json")
742
+ else:
743
+ formatter.format_dict(requirements)
744
+ elif format == "csv":
745
+ # Flatten for CSV output
746
+ flat_data = []
747
+ for org in requirements.get("organizations", []):
748
+ flat_data.append({"type": "organization", **org})
749
+ for marking in requirements.get("markings", []):
750
+ flat_data.append({"type": "marking", **marking})
751
+ if output:
752
+ formatter.save_to_file(flat_data, output, "csv")
753
+ else:
754
+ formatter.format_list(flat_data)
755
+ else:
756
+ _format_access_requirements_table(requirements)
757
+
758
+ if output:
759
+ formatter.print_success(f"Access requirements saved to {output}")
760
+
761
+ except (ProfileNotFoundError, MissingCredentialsError) as e:
762
+ formatter.print_error(f"Authentication error: {e}")
763
+ raise typer.Exit(1)
764
+ except Exception as e:
765
+ formatter.print_error(f"Failed to get access requirements: {e}")
766
+ raise typer.Exit(1)
767
+
768
+
769
+ @app.command("batch-get-by-path")
770
+ def get_resources_by_path_batch(
771
+ paths: List[str] = typer.Argument(
772
+ ..., help="Absolute paths to resources (space-separated)"
773
+ ),
774
+ profile: Optional[str] = typer.Option(
775
+ None, "--profile", help="Profile name", autocompletion=complete_profile
776
+ ),
777
+ format: str = typer.Option(
778
+ "table",
779
+ "--format",
780
+ "-f",
781
+ help="Output format (table, json, csv)",
782
+ autocompletion=complete_output_format,
783
+ ),
784
+ output: Optional[str] = typer.Option(
785
+ None, "--output", "-o", help="Output file path"
786
+ ),
787
+ ):
788
+ """Get multiple resources by their absolute paths (max 1000)."""
789
+ try:
790
+ if len(paths) > 1000:
791
+ formatter.print_error("Maximum batch size is 1000 paths")
792
+ raise typer.Exit(1)
793
+
794
+ service = ResourceService(profile=profile)
795
+
796
+ with SpinnerProgressTracker().track_spinner(
797
+ f"Fetching {len(paths)} resources by path..."
798
+ ):
799
+ resources = service.get_resources_by_path_batch(paths)
800
+
801
+ # Cache RIDs for future completions
802
+ for resource in resources:
803
+ if resource.get("rid"):
804
+ cache_rid(resource["rid"])
805
+
806
+ # Format output
807
+ if format == "json":
808
+ if output:
809
+ formatter.save_to_file(resources, output, "json")
810
+ else:
811
+ formatter.format_list(resources)
812
+ elif format == "csv":
813
+ if output:
814
+ formatter.save_to_file(resources, output, "csv")
815
+ else:
816
+ formatter.format_list(resources)
817
+ else:
818
+ _format_resources_table(resources)
819
+
820
+ if output:
821
+ formatter.print_success(f"Resources information saved to {output}")
822
+
823
+ except (ProfileNotFoundError, MissingCredentialsError) as e:
824
+ formatter.print_error(f"Authentication error: {e}")
825
+ raise typer.Exit(1)
826
+ except ValueError as e:
827
+ formatter.print_error(f"Invalid request: {e}")
828
+ raise typer.Exit(1)
829
+ except Exception as e:
830
+ formatter.print_error(f"Failed to get resources by path batch: {e}")
831
+ raise typer.Exit(1)
832
+
833
+
462
834
  def _format_resource_table(resource: dict):
463
835
  """Format resource information as a table."""
464
836
  table = Table(
@@ -520,6 +892,70 @@ def _format_metadata_table(metadata: dict):
520
892
  console.print(table)
521
893
 
522
894
 
895
+ def _format_markings_table(markings: List[dict]):
896
+ """Format markings as a table."""
897
+ table = Table(title="Markings", show_header=True, header_style="bold cyan")
898
+ table.add_column("Marking ID")
899
+ table.add_column("Display Name")
900
+ table.add_column("Category")
901
+ table.add_column("Description")
902
+
903
+ for marking in markings:
904
+ table.add_row(
905
+ marking.get("marking_id", "N/A"),
906
+ marking.get("display_name", "N/A"),
907
+ marking.get("category_display_name", "N/A"),
908
+ marking.get("description", "N/A"),
909
+ )
910
+
911
+ console.print(table)
912
+ console.print(f"\nTotal: {len(markings)} markings")
913
+
914
+
915
+ def _format_access_requirements_table(requirements: dict):
916
+ """Format access requirements as tables."""
917
+ organizations = requirements.get("organizations", [])
918
+ markings = requirements.get("markings", [])
919
+
920
+ if organizations:
921
+ org_table = Table(
922
+ title="Required Organizations", show_header=True, header_style="bold cyan"
923
+ )
924
+ org_table.add_column("Organization RID")
925
+ org_table.add_column("Display Name")
926
+
927
+ for org in organizations:
928
+ org_table.add_row(
929
+ org.get("organization_rid", "N/A"),
930
+ org.get("display_name", "N/A"),
931
+ )
932
+
933
+ console.print(org_table)
934
+ console.print(f"Total: {len(organizations)} organizations\n")
935
+ else:
936
+ console.print("[dim]No organization requirements[/dim]\n")
937
+
938
+ if markings:
939
+ marking_table = Table(
940
+ title="Required Markings", show_header=True, header_style="bold cyan"
941
+ )
942
+ marking_table.add_column("Marking ID")
943
+ marking_table.add_column("Display Name")
944
+ marking_table.add_column("Category")
945
+
946
+ for marking in markings:
947
+ marking_table.add_row(
948
+ marking.get("marking_id", "N/A"),
949
+ marking.get("display_name", "N/A"),
950
+ marking.get("category_display_name", "N/A"),
951
+ )
952
+
953
+ console.print(marking_table)
954
+ console.print(f"Total: {len(markings)} markings")
955
+ else:
956
+ console.print("[dim]No marking requirements[/dim]")
957
+
958
+
523
959
  @app.callback()
524
960
  def main():
525
961
  """
@@ -556,5 +992,21 @@ def main():
556
992
 
557
993
  # Move resource to different folder
558
994
  pltr resource move ri.compass.main.dataset.xyz123 --target-folder ri.compass.main.folder.new456
995
+
996
+ # Trash operations
997
+ pltr resource delete ri.compass.main.dataset.xyz123
998
+ pltr resource restore ri.compass.main.dataset.xyz123
999
+ pltr resource permanently-delete ri.compass.main.dataset.xyz123
1000
+
1001
+ # Markings operations
1002
+ pltr resource add-markings ri.compass.main.dataset.xyz123 -m marking-id-1 -m marking-id-2
1003
+ pltr resource remove-markings ri.compass.main.dataset.xyz123 -m marking-id-1
1004
+ pltr resource list-markings ri.compass.main.dataset.xyz123
1005
+
1006
+ # Access requirements
1007
+ pltr resource access-requirements ri.compass.main.dataset.xyz123
1008
+
1009
+ # Batch get by path
1010
+ pltr resource batch-get-by-path "/Org/Project/Dataset1" "/Org/Project/Dataset2"
559
1011
  """
560
1012
  pass
pltr/commands/sql.py CHANGED
@@ -42,8 +42,15 @@ def execute_query(
42
42
  fallback_branches: Optional[str] = typer.Option(
43
43
  None, "--fallback-branches", help="Comma-separated list of fallback branch IDs"
44
44
  ),
45
+ preview: bool = typer.Option(
46
+ True,
47
+ "--preview/--no-preview",
48
+ help="Enable preview mode (required for SQL API)",
49
+ ),
45
50
  ) -> None:
46
- """Execute a SQL query and display results."""
51
+ """Execute a SQL query and display results.
52
+
53
+ Note: SQL queries are currently in preview and may be modified or removed at any time."""
47
54
  console = Console()
48
55
  formatter = OutputFormatter()
49
56
 
@@ -62,6 +69,7 @@ def execute_query(
62
69
  fallback_branch_ids=fallback_branch_ids,
63
70
  timeout=timeout,
64
71
  format="table" if output_format in ["table", "csv"] else "json",
72
+ preview=preview,
65
73
  )
66
74
 
67
75
  # Extract results
@@ -92,8 +100,15 @@ def submit_query(
92
100
  fallback_branches: Optional[str] = typer.Option(
93
101
  None, "--fallback-branches", help="Comma-separated list of fallback branch IDs"
94
102
  ),
103
+ preview: bool = typer.Option(
104
+ True,
105
+ "--preview/--no-preview",
106
+ help="Enable preview mode (required for SQL API)",
107
+ ),
95
108
  ) -> None:
96
- """Submit a SQL query without waiting for completion."""
109
+ """Submit a SQL query without waiting for completion.
110
+
111
+ Note: SQL queries are currently in preview and may be modified or removed at any time."""
97
112
  console = Console()
98
113
  formatter = OutputFormatter()
99
114
 
@@ -108,7 +123,7 @@ def submit_query(
108
123
 
109
124
  with SpinnerProgressTracker().track_spinner("Submitting SQL query..."):
110
125
  result = service.submit_query(
111
- query=query, fallback_branch_ids=fallback_branch_ids
126
+ query=query, fallback_branch_ids=fallback_branch_ids, preview=preview
112
127
  )
113
128
 
114
129
  console.print("[green]Query submitted successfully[/green]")
@@ -136,6 +151,11 @@ def get_query_status(
136
151
  profile: Optional[str] = typer.Option(
137
152
  None, "--profile", help="Auth profile to use"
138
153
  ),
154
+ preview: bool = typer.Option(
155
+ True,
156
+ "--preview/--no-preview",
157
+ help="Enable preview mode (required for SQL API)",
158
+ ),
139
159
  ) -> None:
140
160
  """Get the status of a submitted query."""
141
161
  console = Console()
@@ -145,7 +165,7 @@ def get_query_status(
145
165
  service = SqlService(profile=profile)
146
166
 
147
167
  with SpinnerProgressTracker().track_spinner("Checking query status..."):
148
- result = service.get_query_status(query_id)
168
+ result = service.get_query_status(query_id, preview=preview)
149
169
 
150
170
  console.print(f"Query ID: [bold]{query_id}[/bold]")
151
171
 
@@ -183,6 +203,11 @@ def get_query_results(
183
203
  output_file: Optional[Path] = typer.Option(
184
204
  None, "--output", help="Save results to file"
185
205
  ),
206
+ preview: bool = typer.Option(
207
+ True,
208
+ "--preview/--no-preview",
209
+ help="Enable preview mode (required for SQL API)",
210
+ ),
186
211
  ) -> None:
187
212
  """Get the results of a completed query."""
188
213
  console = Console()
@@ -195,6 +220,7 @@ def get_query_results(
195
220
  result = service.get_query_results(
196
221
  query_id,
197
222
  format="table" if output_format in ["table", "csv"] else "json",
223
+ preview=preview,
198
224
  )
199
225
 
200
226
  # Display or save results
@@ -217,6 +243,11 @@ def cancel_query(
217
243
  profile: Optional[str] = typer.Option(
218
244
  None, "--profile", help="Auth profile to use"
219
245
  ),
246
+ preview: bool = typer.Option(
247
+ True,
248
+ "--preview/--no-preview",
249
+ help="Enable preview mode (required for SQL API)",
250
+ ),
220
251
  ) -> None:
221
252
  """Cancel a running query."""
222
253
  console = Console()
@@ -226,7 +257,7 @@ def cancel_query(
226
257
  service = SqlService(profile=profile)
227
258
 
228
259
  with SpinnerProgressTracker().track_spinner("Canceling query..."):
229
- result = service.cancel_query(query_id)
260
+ result = service.cancel_query(query_id, preview=preview)
230
261
 
231
262
  console.print(f"Query ID: [bold]{query_id}[/bold]")
232
263
 
@@ -259,8 +290,15 @@ def export_query_results(
259
290
  fallback_branches: Optional[str] = typer.Option(
260
291
  None, "--fallback-branches", help="Comma-separated list of fallback branch IDs"
261
292
  ),
293
+ preview: bool = typer.Option(
294
+ True,
295
+ "--preview/--no-preview",
296
+ help="Enable preview mode (required for SQL API)",
297
+ ),
262
298
  ) -> None:
263
- """Execute a SQL query and export results to a file."""
299
+ """Execute a SQL query and export results to a file.
300
+
301
+ Note: SQL queries are currently in preview and may be modified or removed at any time."""
264
302
  console = Console()
265
303
  formatter = OutputFormatter()
266
304
 
@@ -289,6 +327,7 @@ def export_query_results(
289
327
  fallback_branch_ids=fallback_branch_ids,
290
328
  timeout=timeout,
291
329
  format="table" if output_format in ["table", "csv"] else "json",
330
+ preview=preview,
292
331
  )
293
332
 
294
333
  # Save results to file
@@ -321,6 +360,11 @@ def wait_for_query(
321
360
  output_file: Optional[Path] = typer.Option(
322
361
  None, "--output", help="Save results to file when completed"
323
362
  ),
363
+ preview: bool = typer.Option(
364
+ True,
365
+ "--preview/--no-preview",
366
+ help="Enable preview mode (required for SQL API)",
367
+ ),
324
368
  ) -> None:
325
369
  """Wait for a query to complete and optionally display results."""
326
370
  console = Console()
@@ -330,7 +374,9 @@ def wait_for_query(
330
374
  service = SqlService(profile=profile)
331
375
 
332
376
  with SpinnerProgressTracker().track_spinner("Waiting for query to complete..."):
333
- status_result = service.wait_for_completion(query_id, timeout)
377
+ status_result = service.wait_for_completion(
378
+ query_id, timeout, preview=preview
379
+ )
334
380
 
335
381
  console.print(f"Query ID: [bold]{query_id}[/bold]")
336
382
  console.print(
@@ -343,6 +389,7 @@ def wait_for_query(
343
389
  result = service.get_query_results(
344
390
  query_id,
345
391
  format="table" if output_format in ["table", "csv"] else "json",
392
+ preview=preview,
346
393
  )
347
394
 
348
395
  if output_file: