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.
@@ -9,6 +9,7 @@ from rich.console import Console
9
9
 
10
10
  from ..services.orchestration import OrchestrationService
11
11
  from ..utils.formatting import OutputFormatter
12
+ from ..utils.pagination import PaginationConfig
12
13
  from ..utils.progress import SpinnerProgressTracker
13
14
  from ..auth.base import ProfileNotFoundError, MissingCredentialsError
14
15
  from ..utils.completion import (
@@ -226,7 +227,7 @@ def get_build_jobs(
226
227
 
227
228
  if response.get("next_page_token"):
228
229
  formatter.print_info(
229
- "More results available. Use pagination token to fetch next page."
230
+ f"More results available. Use --page-token {response['next_page_token']} to fetch next page."
230
231
  )
231
232
 
232
233
  except (ProfileNotFoundError, MissingCredentialsError) as e:
@@ -241,7 +242,16 @@ def get_build_jobs(
241
242
  def search_builds(
242
243
  profile: Optional[str] = typer.Option(None, "--profile", "-p", help="Profile name"),
243
244
  page_size: Optional[int] = typer.Option(
244
- None, "--page-size", help="Number of results per page"
245
+ None, "--page-size", help="Number of builds per page (default: from settings)"
246
+ ),
247
+ max_pages: Optional[int] = typer.Option(
248
+ 1, "--max-pages", help="Maximum number of pages to fetch (default: 1)"
249
+ ),
250
+ page_token: Optional[str] = typer.Option(
251
+ None, "--page-token", help="Page token to resume from previous response"
252
+ ),
253
+ all: bool = typer.Option(
254
+ False, "--all", help="Fetch all available pages (overrides --max-pages)"
245
255
  ),
246
256
  format: str = typer.Option(
247
257
  "table", "--format", "-f", help="Output format (table, json, csv)"
@@ -250,12 +260,102 @@ def search_builds(
250
260
  None, "--output", "-o", help="Output file path"
251
261
  ),
252
262
  ):
253
- """Search for builds."""
263
+ """
264
+ Search for builds with pagination support.
265
+
266
+ By default, fetches only the first page of results. Use --all to fetch all builds,
267
+ or --max-pages to control how many pages to fetch.
268
+
269
+ Examples:
270
+ # Search first page of builds (default)
271
+ pltr orchestration builds search
272
+
273
+ # Search all builds
274
+ pltr orchestration builds search --all
275
+
276
+ # Search first 3 pages
277
+ pltr orchestration builds search --max-pages 3
278
+
279
+ # Resume from a specific page
280
+ pltr orchestration builds search --page-token abc123
281
+ """
254
282
  try:
255
283
  service = OrchestrationService(profile=profile)
256
284
 
285
+ # Create pagination config
286
+ config = PaginationConfig(
287
+ page_size=page_size,
288
+ max_pages=max_pages,
289
+ page_token=page_token,
290
+ fetch_all=all,
291
+ )
292
+
257
293
  with SpinnerProgressTracker().track_spinner("Searching builds..."):
258
- response = service.search_builds(page_size=page_size)
294
+ result = service.search_builds_paginated(config)
295
+
296
+ if not result.data:
297
+ formatter.print_warning("No builds found")
298
+ return
299
+
300
+ # Format and display paginated results
301
+ if output:
302
+ formatter.format_paginated_output(
303
+ result,
304
+ format,
305
+ output,
306
+ formatter_fn=lambda data, fmt, out: formatter.format_builds_list(
307
+ data, fmt, out
308
+ ),
309
+ )
310
+ formatter.print_success(f"Builds list saved to {output}")
311
+ else:
312
+ formatter.format_paginated_output(
313
+ result,
314
+ format,
315
+ formatter_fn=lambda data, fmt, out: formatter.format_builds_list(
316
+ data, fmt, out
317
+ ),
318
+ )
319
+
320
+ except (ProfileNotFoundError, MissingCredentialsError) as e:
321
+ formatter.print_error(f"Authentication error: {e}")
322
+ raise typer.Exit(1)
323
+ except Exception as e:
324
+ formatter.print_error(f"Failed to search builds: {e}")
325
+ raise typer.Exit(1)
326
+
327
+
328
+ @builds_app.command("get-batch")
329
+ def get_builds_batch(
330
+ build_rids: str = typer.Argument(
331
+ ..., help="Comma-separated list of Build RIDs (max 100)"
332
+ ),
333
+ profile: Optional[str] = typer.Option(None, "--profile", "-p", help="Profile name"),
334
+ format: str = typer.Option(
335
+ "table", "--format", "-f", help="Output format (table, json, csv)"
336
+ ),
337
+ output: Optional[str] = typer.Option(
338
+ None, "--output", "-o", help="Output file path"
339
+ ),
340
+ ):
341
+ """Get multiple builds in batch (max 100)."""
342
+ try:
343
+ rids_list = [rid.strip() for rid in build_rids.split(",") if rid.strip()]
344
+
345
+ if not rids_list:
346
+ formatter.print_error("No valid RIDs provided")
347
+ raise typer.Exit(1)
348
+
349
+ if len(rids_list) > 100:
350
+ formatter.print_error("Maximum batch size is 100 builds")
351
+ raise typer.Exit(1)
352
+
353
+ service = OrchestrationService(profile=profile)
354
+
355
+ with SpinnerProgressTracker().track_spinner(
356
+ f"Fetching {len(rids_list)} builds..."
357
+ ):
358
+ response = service.get_builds_batch(rids_list)
259
359
 
260
360
  builds = response.get("builds", [])
261
361
 
@@ -266,18 +366,13 @@ def search_builds(
266
366
  formatter.format_builds_list(builds, format, output)
267
367
 
268
368
  if output:
269
- formatter.print_success(f"Builds list saved to {output}")
270
-
271
- if response.get("next_page_token"):
272
- formatter.print_info(
273
- "More results available. Use pagination token to fetch next page."
274
- )
369
+ formatter.print_success(f"Builds information saved to {output}")
275
370
 
276
371
  except (ProfileNotFoundError, MissingCredentialsError) as e:
277
372
  formatter.print_error(f"Authentication error: {e}")
278
373
  raise typer.Exit(1)
279
374
  except Exception as e:
280
- formatter.print_error(f"Failed to search builds: {e}")
375
+ formatter.print_error(f"Failed to get builds batch: {e}")
281
376
  raise typer.Exit(1)
282
377
 
283
378
 
@@ -655,6 +750,67 @@ def replace_schedule(
655
750
  raise typer.Exit(1)
656
751
 
657
752
 
753
+ @schedules_app.command("runs")
754
+ def get_schedule_runs(
755
+ schedule_rid: str = typer.Argument(
756
+ ..., help="Schedule Resource Identifier", autocompletion=complete_rid
757
+ ),
758
+ profile: Optional[str] = typer.Option(
759
+ None, "--profile", "-p", help="Profile name", autocompletion=complete_profile
760
+ ),
761
+ page_size: Optional[int] = typer.Option(
762
+ None, "--page-size", help="Number of results per page"
763
+ ),
764
+ page_token: Optional[str] = typer.Option(
765
+ None, "--page-token", help="Pagination token for next page"
766
+ ),
767
+ format: str = typer.Option(
768
+ "table",
769
+ "--format",
770
+ "-f",
771
+ help="Output format (table, json, csv)",
772
+ autocompletion=complete_output_format,
773
+ ),
774
+ output: Optional[str] = typer.Option(
775
+ None, "--output", "-o", help="Output file path"
776
+ ),
777
+ ):
778
+ """List recent execution runs for a schedule."""
779
+ try:
780
+ cache_rid(schedule_rid)
781
+ service = OrchestrationService(profile=profile)
782
+
783
+ with SpinnerProgressTracker().track_spinner(
784
+ f"Fetching runs for schedule {schedule_rid}..."
785
+ ):
786
+ response = service.get_schedule_runs(
787
+ schedule_rid, page_size=page_size, page_token=page_token
788
+ )
789
+
790
+ runs = response.get("runs", [])
791
+
792
+ if not runs:
793
+ formatter.print_warning("No runs found for this schedule")
794
+ return
795
+
796
+ formatter.format_schedule_runs_list(runs, format, output)
797
+
798
+ if output:
799
+ formatter.print_success(f"Runs list saved to {output}")
800
+
801
+ if response.get("next_page_token"):
802
+ formatter.print_info(
803
+ f"More results available. Use --page-token {response['next_page_token']} to fetch next page."
804
+ )
805
+
806
+ except (ProfileNotFoundError, MissingCredentialsError) as e:
807
+ formatter.print_error(f"Authentication error: {e}")
808
+ raise typer.Exit(1)
809
+ except Exception as e:
810
+ formatter.print_error(f"Failed to get schedule runs: {e}")
811
+ raise typer.Exit(1)
812
+
813
+
658
814
  # ============================================================================
659
815
  # Main app setup
660
816
  # ============================================================================
pltr/commands/project.py CHANGED
@@ -366,6 +366,229 @@ def get_projects_batch(
366
366
  raise typer.Exit(1)
367
367
 
368
368
 
369
+ # ==================== Organization Operations ====================
370
+
371
+
372
+ @app.command("add-orgs")
373
+ def add_organizations(
374
+ project_rid: str = typer.Argument(
375
+ ..., help="Project Resource Identifier", autocompletion=complete_rid
376
+ ),
377
+ organization_rids: List[str] = typer.Option(
378
+ ...,
379
+ "--org",
380
+ "-o",
381
+ help="Organization RID(s) to add (can specify multiple)",
382
+ ),
383
+ profile: Optional[str] = typer.Option(
384
+ None, "--profile", help="Profile name", autocompletion=complete_profile
385
+ ),
386
+ ):
387
+ """Add organizations to a project."""
388
+ try:
389
+ service = ProjectService(profile=profile)
390
+
391
+ with SpinnerProgressTracker().track_spinner(
392
+ f"Adding {len(organization_rids)} organization(s) to project {project_rid}..."
393
+ ):
394
+ service.add_organizations(project_rid, organization_rids)
395
+
396
+ formatter.print_success(
397
+ f"Successfully added {len(organization_rids)} organization(s) to project {project_rid}"
398
+ )
399
+
400
+ except (ProfileNotFoundError, MissingCredentialsError) as e:
401
+ formatter.print_error(f"Authentication error: {e}")
402
+ raise typer.Exit(1)
403
+ except Exception as e:
404
+ formatter.print_error(f"Failed to add organizations: {e}")
405
+ raise typer.Exit(1)
406
+
407
+
408
+ @app.command("remove-orgs")
409
+ def remove_organizations(
410
+ project_rid: str = typer.Argument(
411
+ ..., help="Project Resource Identifier", autocompletion=complete_rid
412
+ ),
413
+ organization_rids: List[str] = typer.Option(
414
+ ...,
415
+ "--org",
416
+ "-o",
417
+ help="Organization RID(s) to remove (can specify multiple)",
418
+ ),
419
+ profile: Optional[str] = typer.Option(
420
+ None, "--profile", help="Profile name", autocompletion=complete_profile
421
+ ),
422
+ ):
423
+ """Remove organizations from a project."""
424
+ try:
425
+ service = ProjectService(profile=profile)
426
+
427
+ with SpinnerProgressTracker().track_spinner(
428
+ f"Removing {len(organization_rids)} organization(s) from project {project_rid}..."
429
+ ):
430
+ service.remove_organizations(project_rid, organization_rids)
431
+
432
+ formatter.print_success(
433
+ f"Successfully removed {len(organization_rids)} organization(s) from project {project_rid}"
434
+ )
435
+
436
+ except (ProfileNotFoundError, MissingCredentialsError) as e:
437
+ formatter.print_error(f"Authentication error: {e}")
438
+ raise typer.Exit(1)
439
+ except Exception as e:
440
+ formatter.print_error(f"Failed to remove organizations: {e}")
441
+ raise typer.Exit(1)
442
+
443
+
444
+ @app.command("list-orgs")
445
+ def list_organizations(
446
+ project_rid: str = typer.Argument(
447
+ ..., help="Project Resource Identifier", autocompletion=complete_rid
448
+ ),
449
+ profile: Optional[str] = typer.Option(
450
+ None, "--profile", help="Profile name", autocompletion=complete_profile
451
+ ),
452
+ format: str = typer.Option(
453
+ "table",
454
+ "--format",
455
+ "-f",
456
+ help="Output format (table, json, csv)",
457
+ autocompletion=complete_output_format,
458
+ ),
459
+ output: Optional[str] = typer.Option(None, "--output", help="Output file path"),
460
+ page_size: Optional[int] = typer.Option(
461
+ None, "--page-size", help="Number of items per page"
462
+ ),
463
+ ):
464
+ """List organizations directly applied to a project."""
465
+ try:
466
+ service = ProjectService(profile=profile)
467
+
468
+ with SpinnerProgressTracker().track_spinner(
469
+ f"Fetching organizations for project {project_rid}..."
470
+ ):
471
+ organizations = service.list_organizations(project_rid, page_size=page_size)
472
+
473
+ if not organizations:
474
+ formatter.print_info("No organizations found on this project.")
475
+ return
476
+
477
+ # Format output
478
+ if format == "json":
479
+ if output:
480
+ formatter.save_to_file(organizations, output, "json")
481
+ else:
482
+ formatter.format_list(organizations)
483
+ elif format == "csv":
484
+ if output:
485
+ formatter.save_to_file(organizations, output, "csv")
486
+ else:
487
+ formatter.format_list(organizations)
488
+ else:
489
+ _format_organizations_table(organizations)
490
+
491
+ if output:
492
+ formatter.print_success(f"Organizations list saved to {output}")
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 list organizations: {e}")
499
+ raise typer.Exit(1)
500
+
501
+
502
+ # ==================== Template Operations ====================
503
+
504
+
505
+ @app.command("create-from-template")
506
+ def create_from_template(
507
+ template_rid: str = typer.Option(
508
+ ...,
509
+ "--template-rid",
510
+ "-t",
511
+ help="Template Resource Identifier",
512
+ autocompletion=complete_rid,
513
+ ),
514
+ variable: List[str] = typer.Option(
515
+ ...,
516
+ "--var",
517
+ "-v",
518
+ help="Variable values in format 'name=value' (can specify multiple)",
519
+ ),
520
+ description: Optional[str] = typer.Option(
521
+ None, "--description", "-d", help="Project description"
522
+ ),
523
+ organization_rids: Optional[List[str]] = typer.Option(
524
+ None,
525
+ "--org",
526
+ "-o",
527
+ help="Organization RIDs (can be specified multiple times)",
528
+ ),
529
+ profile: Optional[str] = typer.Option(
530
+ None, "--profile", help="Profile name", autocompletion=complete_profile
531
+ ),
532
+ format: str = typer.Option(
533
+ "table",
534
+ "--format",
535
+ "-f",
536
+ help="Output format (table, json, csv)",
537
+ autocompletion=complete_output_format,
538
+ ),
539
+ ):
540
+ """Create a project from a template."""
541
+ try:
542
+ # Parse variable values
543
+ variable_values = {}
544
+ for var in variable:
545
+ if "=" not in var:
546
+ formatter.print_error(
547
+ f"Invalid variable format: '{var}'. Use 'name=value' format."
548
+ )
549
+ raise typer.Exit(1)
550
+ name, value = var.split("=", 1)
551
+ name = name.strip()
552
+ if not name:
553
+ formatter.print_error(f"Variable name cannot be empty: '{var}'")
554
+ raise typer.Exit(1)
555
+ variable_values[name] = value
556
+
557
+ service = ProjectService(profile=profile)
558
+
559
+ with SpinnerProgressTracker().track_spinner(
560
+ f"Creating project from template {template_rid}..."
561
+ ):
562
+ project = service.create_project_from_template(
563
+ template_rid=template_rid,
564
+ variable_values=variable_values,
565
+ organization_rids=organization_rids,
566
+ project_description=description,
567
+ )
568
+
569
+ # Cache the RID for future completions
570
+ if project.get("rid"):
571
+ cache_rid(project["rid"])
572
+
573
+ formatter.print_success("Successfully created project from template")
574
+ formatter.print_info(f"Project RID: {project.get('rid', 'unknown')}")
575
+
576
+ # Format output
577
+ if format == "json":
578
+ formatter.format_dict(project)
579
+ elif format == "csv":
580
+ formatter.format_list([project])
581
+ else:
582
+ _format_project_table(project)
583
+
584
+ except (ProfileNotFoundError, MissingCredentialsError) as e:
585
+ formatter.print_error(f"Authentication error: {e}")
586
+ raise typer.Exit(1)
587
+ except Exception as e:
588
+ formatter.print_error(f"Failed to create project from template: {e}")
589
+ raise typer.Exit(1)
590
+
591
+
369
592
  def _format_project_table(project: dict):
370
593
  """Format project information as a table."""
371
594
  table = Table(
@@ -410,6 +633,24 @@ def _format_projects_table(projects: List[dict]):
410
633
  console.print(f"\nTotal: {len(projects)} projects")
411
634
 
412
635
 
636
+ def _format_organizations_table(organizations: List[dict]):
637
+ """Format organizations as a table."""
638
+ table = Table(title="Organizations", show_header=True, header_style="bold cyan")
639
+ table.add_column("Organization RID")
640
+ table.add_column("Display Name")
641
+ table.add_column("Description")
642
+
643
+ for org in organizations:
644
+ table.add_row(
645
+ org.get("organization_rid", "N/A"),
646
+ org.get("display_name", "N/A"),
647
+ org.get("description", "N/A"),
648
+ )
649
+
650
+ console.print(table)
651
+ console.print(f"\nTotal: {len(organizations)} organizations")
652
+
653
+
413
654
  @app.callback()
414
655
  def main():
415
656
  """
@@ -436,5 +677,13 @@ def main():
436
677
 
437
678
  # Delete project
438
679
  pltr project delete ri.compass.main.project.abc456
680
+
681
+ # Organization operations
682
+ pltr project add-orgs ri.compass.main.project.abc456 -o org-rid-1 -o org-rid-2
683
+ pltr project remove-orgs ri.compass.main.project.abc456 -o org-rid-1
684
+ pltr project list-orgs ri.compass.main.project.abc456
685
+
686
+ # Create from template
687
+ pltr project create-from-template -t template-rid -v "name=MyProject" -v "desc=Description"
439
688
  """
440
689
  pass