pltr-cli 0.9.3__tar.gz → 0.11.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 (128) hide show
  1. {pltr_cli-0.9.3 → pltr_cli-0.11.0}/PKG-INFO +1 -1
  2. {pltr_cli-0.9.3 → pltr_cli-0.11.0}/pyproject.toml +1 -1
  3. pltr_cli-0.11.0/src/pltr/__init__.py +1 -0
  4. {pltr_cli-0.9.3 → pltr_cli-0.11.0}/src/pltr/commands/folder.py +3 -3
  5. {pltr_cli-0.9.3 → pltr_cli-0.11.0}/src/pltr/commands/ontology.py +93 -0
  6. {pltr_cli-0.9.3 → pltr_cli-0.11.0}/src/pltr/commands/orchestration.py +40 -1
  7. {pltr_cli-0.9.3 → pltr_cli-0.11.0}/src/pltr/commands/resource.py +63 -2
  8. {pltr_cli-0.9.3 → pltr_cli-0.11.0}/src/pltr/services/dataset.py +5 -8
  9. {pltr_cli-0.9.3 → pltr_cli-0.11.0}/src/pltr/services/ontology.py +71 -0
  10. {pltr_cli-0.9.3 → pltr_cli-0.11.0}/src/pltr/services/resource.py +16 -0
  11. {pltr_cli-0.9.3 → pltr_cli-0.11.0}/src/pltr/utils/formatting.py +9 -3
  12. {pltr_cli-0.9.3 → pltr_cli-0.11.0}/tests/test_commands/test_ontology.py +105 -0
  13. {pltr_cli-0.9.3 → pltr_cli-0.11.0}/tests/test_services/test_ontology.py +79 -0
  14. {pltr_cli-0.9.3 → pltr_cli-0.11.0}/tests/test_services/test_resource.py +36 -0
  15. {pltr_cli-0.9.3 → pltr_cli-0.11.0}/uv.lock +1 -1
  16. pltr_cli-0.9.3/src/pltr/__init__.py +0 -1
  17. {pltr_cli-0.9.3 → pltr_cli-0.11.0}/.github/workflows/ci.yml +0 -0
  18. {pltr_cli-0.9.3 → pltr_cli-0.11.0}/.github/workflows/publish.yml +0 -0
  19. {pltr_cli-0.9.3 → pltr_cli-0.11.0}/.github/workflows/test-publish.yml +0 -0
  20. {pltr_cli-0.9.3 → pltr_cli-0.11.0}/.gitignore +0 -0
  21. {pltr_cli-0.9.3 → pltr_cli-0.11.0}/.pre-commit-config.yaml +0 -0
  22. {pltr_cli-0.9.3 → pltr_cli-0.11.0}/CHANGELOG.md +0 -0
  23. {pltr_cli-0.9.3 → pltr_cli-0.11.0}/CLAUDE.md +0 -0
  24. {pltr_cli-0.9.3 → pltr_cli-0.11.0}/LICENSE +0 -0
  25. {pltr_cli-0.9.3 → pltr_cli-0.11.0}/README.md +0 -0
  26. {pltr_cli-0.9.3 → pltr_cli-0.11.0}/RELEASE.md +0 -0
  27. {pltr_cli-0.9.3 → pltr_cli-0.11.0}/children.csv +0 -0
  28. {pltr_cli-0.9.3 → pltr_cli-0.11.0}/docs/README.md +0 -0
  29. {pltr_cli-0.9.3 → pltr_cli-0.11.0}/docs/api/wrapper.md +0 -0
  30. {pltr_cli-0.9.3 → pltr_cli-0.11.0}/docs/examples/csv-upload.md +0 -0
  31. {pltr_cli-0.9.3 → pltr_cli-0.11.0}/docs/examples/gallery.md +0 -0
  32. {pltr_cli-0.9.3 → pltr_cli-0.11.0}/docs/features/dataset-transactions.md +0 -0
  33. {pltr_cli-0.9.3 → pltr_cli-0.11.0}/docs/user-guide/aliases.md +0 -0
  34. {pltr_cli-0.9.3 → pltr_cli-0.11.0}/docs/user-guide/authentication.md +0 -0
  35. {pltr_cli-0.9.3 → pltr_cli-0.11.0}/docs/user-guide/commands.md +0 -0
  36. {pltr_cli-0.9.3 → pltr_cli-0.11.0}/docs/user-guide/quick-start.md +0 -0
  37. {pltr_cli-0.9.3 → pltr_cli-0.11.0}/docs/user-guide/troubleshooting.md +0 -0
  38. {pltr_cli-0.9.3 → pltr_cli-0.11.0}/docs/user-guide/workflows.md +0 -0
  39. {pltr_cli-0.9.3 → pltr_cli-0.11.0}/folder_info.json +0 -0
  40. {pltr_cli-0.9.3 → pltr_cli-0.11.0}/folders.json +0 -0
  41. {pltr_cli-0.9.3 → pltr_cli-0.11.0}/mypy.ini +0 -0
  42. {pltr_cli-0.9.3 → pltr_cli-0.11.0}/scripts/release.py +0 -0
  43. {pltr_cli-0.9.3 → pltr_cli-0.11.0}/src/pltr/__main__.py +0 -0
  44. {pltr_cli-0.9.3 → pltr_cli-0.11.0}/src/pltr/auth/__init__.py +0 -0
  45. {pltr_cli-0.9.3 → pltr_cli-0.11.0}/src/pltr/auth/base.py +0 -0
  46. {pltr_cli-0.9.3 → pltr_cli-0.11.0}/src/pltr/auth/manager.py +0 -0
  47. {pltr_cli-0.9.3 → pltr_cli-0.11.0}/src/pltr/auth/oauth.py +0 -0
  48. {pltr_cli-0.9.3 → pltr_cli-0.11.0}/src/pltr/auth/storage.py +0 -0
  49. {pltr_cli-0.9.3 → pltr_cli-0.11.0}/src/pltr/auth/token.py +0 -0
  50. {pltr_cli-0.9.3 → pltr_cli-0.11.0}/src/pltr/cli.py +0 -0
  51. {pltr_cli-0.9.3 → pltr_cli-0.11.0}/src/pltr/commands/__init__.py +0 -0
  52. {pltr_cli-0.9.3 → pltr_cli-0.11.0}/src/pltr/commands/admin.py +0 -0
  53. {pltr_cli-0.9.3 → pltr_cli-0.11.0}/src/pltr/commands/alias.py +0 -0
  54. {pltr_cli-0.9.3 → pltr_cli-0.11.0}/src/pltr/commands/completion.py +0 -0
  55. {pltr_cli-0.9.3 → pltr_cli-0.11.0}/src/pltr/commands/configure.py +0 -0
  56. {pltr_cli-0.9.3 → pltr_cli-0.11.0}/src/pltr/commands/connectivity.py +0 -0
  57. {pltr_cli-0.9.3 → pltr_cli-0.11.0}/src/pltr/commands/dataset.py +0 -0
  58. {pltr_cli-0.9.3 → pltr_cli-0.11.0}/src/pltr/commands/mediasets.py +0 -0
  59. {pltr_cli-0.9.3 → pltr_cli-0.11.0}/src/pltr/commands/project.py +0 -0
  60. {pltr_cli-0.9.3 → pltr_cli-0.11.0}/src/pltr/commands/resource_role.py +0 -0
  61. {pltr_cli-0.9.3 → pltr_cli-0.11.0}/src/pltr/commands/shell.py +0 -0
  62. {pltr_cli-0.9.3 → pltr_cli-0.11.0}/src/pltr/commands/space.py +0 -0
  63. {pltr_cli-0.9.3 → pltr_cli-0.11.0}/src/pltr/commands/sql.py +0 -0
  64. {pltr_cli-0.9.3 → pltr_cli-0.11.0}/src/pltr/commands/verify.py +0 -0
  65. {pltr_cli-0.9.3 → pltr_cli-0.11.0}/src/pltr/config/__init__.py +0 -0
  66. {pltr_cli-0.9.3 → pltr_cli-0.11.0}/src/pltr/config/aliases.py +0 -0
  67. {pltr_cli-0.9.3 → pltr_cli-0.11.0}/src/pltr/config/profiles.py +0 -0
  68. {pltr_cli-0.9.3 → pltr_cli-0.11.0}/src/pltr/config/settings.py +0 -0
  69. {pltr_cli-0.9.3 → pltr_cli-0.11.0}/src/pltr/services/__init__.py +0 -0
  70. {pltr_cli-0.9.3 → pltr_cli-0.11.0}/src/pltr/services/admin.py +0 -0
  71. {pltr_cli-0.9.3 → pltr_cli-0.11.0}/src/pltr/services/base.py +0 -0
  72. {pltr_cli-0.9.3 → pltr_cli-0.11.0}/src/pltr/services/connectivity.py +0 -0
  73. {pltr_cli-0.9.3 → pltr_cli-0.11.0}/src/pltr/services/folder.py +0 -0
  74. {pltr_cli-0.9.3 → pltr_cli-0.11.0}/src/pltr/services/mediasets.py +0 -0
  75. {pltr_cli-0.9.3 → pltr_cli-0.11.0}/src/pltr/services/orchestration.py +0 -0
  76. {pltr_cli-0.9.3 → pltr_cli-0.11.0}/src/pltr/services/project.py +0 -0
  77. {pltr_cli-0.9.3 → pltr_cli-0.11.0}/src/pltr/services/resource_role.py +0 -0
  78. {pltr_cli-0.9.3 → pltr_cli-0.11.0}/src/pltr/services/space.py +0 -0
  79. {pltr_cli-0.9.3 → pltr_cli-0.11.0}/src/pltr/services/sql.py +0 -0
  80. {pltr_cli-0.9.3 → pltr_cli-0.11.0}/src/pltr/utils/__init__.py +0 -0
  81. {pltr_cli-0.9.3 → pltr_cli-0.11.0}/src/pltr/utils/alias_resolver.py +0 -0
  82. {pltr_cli-0.9.3 → pltr_cli-0.11.0}/src/pltr/utils/completion.py +0 -0
  83. {pltr_cli-0.9.3 → pltr_cli-0.11.0}/src/pltr/utils/progress.py +0 -0
  84. {pltr_cli-0.9.3 → pltr_cli-0.11.0}/tests/__init__.py +0 -0
  85. {pltr_cli-0.9.3 → pltr_cli-0.11.0}/tests/conftest.py +0 -0
  86. {pltr_cli-0.9.3 → pltr_cli-0.11.0}/tests/integration/README.md +0 -0
  87. {pltr_cli-0.9.3 → pltr_cli-0.11.0}/tests/integration/__init__.py +0 -0
  88. {pltr_cli-0.9.3 → pltr_cli-0.11.0}/tests/integration/conftest.py +0 -0
  89. {pltr_cli-0.9.3 → pltr_cli-0.11.0}/tests/integration/test_auth_flow.py +0 -0
  90. {pltr_cli-0.9.3 → pltr_cli-0.11.0}/tests/integration/test_cli_integration.py +0 -0
  91. {pltr_cli-0.9.3 → pltr_cli-0.11.0}/tests/integration/test_data_workflows.py +0 -0
  92. {pltr_cli-0.9.3 → pltr_cli-0.11.0}/tests/integration/test_data_workflows_simple.py +0 -0
  93. {pltr_cli-0.9.3 → pltr_cli-0.11.0}/tests/integration/test_simple_integration.py +0 -0
  94. {pltr_cli-0.9.3 → pltr_cli-0.11.0}/tests/test_auth/__init__.py +0 -0
  95. {pltr_cli-0.9.3 → pltr_cli-0.11.0}/tests/test_auth/test_base.py +0 -0
  96. {pltr_cli-0.9.3 → pltr_cli-0.11.0}/tests/test_auth/test_manager.py +0 -0
  97. {pltr_cli-0.9.3 → pltr_cli-0.11.0}/tests/test_auth/test_oauth.py +0 -0
  98. {pltr_cli-0.9.3 → pltr_cli-0.11.0}/tests/test_auth/test_storage.py +0 -0
  99. {pltr_cli-0.9.3 → pltr_cli-0.11.0}/tests/test_auth/test_token.py +0 -0
  100. {pltr_cli-0.9.3 → pltr_cli-0.11.0}/tests/test_commands/__init__.py +0 -0
  101. {pltr_cli-0.9.3 → pltr_cli-0.11.0}/tests/test_commands/test_admin.py +0 -0
  102. {pltr_cli-0.9.3 → pltr_cli-0.11.0}/tests/test_commands/test_alias.py +0 -0
  103. {pltr_cli-0.9.3 → pltr_cli-0.11.0}/tests/test_commands/test_completion.py +0 -0
  104. {pltr_cli-0.9.3 → pltr_cli-0.11.0}/tests/test_commands/test_connectivity.py +0 -0
  105. {pltr_cli-0.9.3 → pltr_cli-0.11.0}/tests/test_commands/test_dataset.py +0 -0
  106. {pltr_cli-0.9.3 → pltr_cli-0.11.0}/tests/test_commands/test_folder.py +0 -0
  107. {pltr_cli-0.9.3 → pltr_cli-0.11.0}/tests/test_commands/test_orchestration.py +0 -0
  108. {pltr_cli-0.9.3 → pltr_cli-0.11.0}/tests/test_commands/test_shell.py +0 -0
  109. {pltr_cli-0.9.3 → pltr_cli-0.11.0}/tests/test_commands/test_sql.py +0 -0
  110. {pltr_cli-0.9.3 → pltr_cli-0.11.0}/tests/test_commands/test_verify_simple.py +0 -0
  111. {pltr_cli-0.9.3 → pltr_cli-0.11.0}/tests/test_config/__init__.py +0 -0
  112. {pltr_cli-0.9.3 → pltr_cli-0.11.0}/tests/test_config/test_aliases.py +0 -0
  113. {pltr_cli-0.9.3 → pltr_cli-0.11.0}/tests/test_config/test_profiles.py +0 -0
  114. {pltr_cli-0.9.3 → pltr_cli-0.11.0}/tests/test_config/test_settings.py +0 -0
  115. {pltr_cli-0.9.3 → pltr_cli-0.11.0}/tests/test_services/__init__.py +0 -0
  116. {pltr_cli-0.9.3 → pltr_cli-0.11.0}/tests/test_services/test_admin.py +0 -0
  117. {pltr_cli-0.9.3 → pltr_cli-0.11.0}/tests/test_services/test_base.py +0 -0
  118. {pltr_cli-0.9.3 → pltr_cli-0.11.0}/tests/test_services/test_connectivity.py +0 -0
  119. {pltr_cli-0.9.3 → pltr_cli-0.11.0}/tests/test_services/test_dataset.py +0 -0
  120. {pltr_cli-0.9.3 → pltr_cli-0.11.0}/tests/test_services/test_dataset_transactions.py +0 -0
  121. {pltr_cli-0.9.3 → pltr_cli-0.11.0}/tests/test_services/test_folder.py +0 -0
  122. {pltr_cli-0.9.3 → pltr_cli-0.11.0}/tests/test_services/test_orchestration.py +0 -0
  123. {pltr_cli-0.9.3 → pltr_cli-0.11.0}/tests/test_services/test_project.py +0 -0
  124. {pltr_cli-0.9.3 → pltr_cli-0.11.0}/tests/test_services/test_resource_role.py +0 -0
  125. {pltr_cli-0.9.3 → pltr_cli-0.11.0}/tests/test_services/test_space.py +0 -0
  126. {pltr_cli-0.9.3 → pltr_cli-0.11.0}/tests/test_services/test_sql.py +0 -0
  127. {pltr_cli-0.9.3 → pltr_cli-0.11.0}/tests/test_utils/__init__.py +0 -0
  128. {pltr_cli-0.9.3 → pltr_cli-0.11.0}/tests/test_utils/test_alias_resolver.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pltr-cli
3
- Version: 0.9.3
3
+ Version: 0.11.0
4
4
  Summary: Command-line interface for Palantir Foundry APIs
5
5
  Project-URL: Homepage, https://github.com/anjor/pltr-cli
6
6
  Project-URL: Repository, https://github.com/anjor/pltr-cli
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "pltr-cli"
3
- version = "0.9.3"
3
+ version = "0.11.0"
4
4
  description = "Command-line interface for Palantir Foundry APIs"
5
5
  authors = [
6
6
  { name = "anjor", email = "anjor@umd.edu" },
@@ -0,0 +1 @@
1
+ __version__ = "0.11.0"
@@ -279,7 +279,7 @@ def _format_children_table(children: List[dict]):
279
279
  table = Table(title="Folder Children", show_header=True, header_style="bold cyan")
280
280
  table.add_column("Type", style="cyan")
281
281
  table.add_column("Display Name")
282
- table.add_column("RID")
282
+ table.add_column("RID", no_wrap=True)
283
283
 
284
284
  for child in children:
285
285
  table.add_row(
@@ -296,8 +296,8 @@ def _format_folders_batch_table(folders: List[dict]):
296
296
  """Format multiple folders as a table."""
297
297
  table = Table(title="Folders", show_header=True, header_style="bold cyan")
298
298
  table.add_column("Display Name")
299
- table.add_column("RID")
300
- table.add_column("Parent Folder")
299
+ table.add_column("RID", no_wrap=True)
300
+ table.add_column("Parent Folder", no_wrap=True)
301
301
  table.add_column("Description")
302
302
 
303
303
  for folder in folders:
@@ -366,6 +366,99 @@ def list_linked_objects(
366
366
  raise typer.Exit(1)
367
367
 
368
368
 
369
+ @app.command("object-count")
370
+ def count_objects(
371
+ ontology_rid: str = typer.Argument(..., help="Ontology Resource Identifier"),
372
+ object_type: str = typer.Argument(..., help="Object type API name"),
373
+ profile: Optional[str] = typer.Option(None, "--profile", "-p", help="Profile name"),
374
+ format: str = typer.Option(
375
+ "table", "--format", "-f", help="Output format (table, json, csv)"
376
+ ),
377
+ output: Optional[str] = typer.Option(
378
+ None, "--output", "-o", help="Output file path"
379
+ ),
380
+ branch: Optional[str] = typer.Option(None, "--branch", "-b", help="Branch name"),
381
+ ):
382
+ """Count objects of a specific type."""
383
+ try:
384
+ service = OntologyObjectService(profile=profile)
385
+
386
+ with SpinnerProgressTracker().track_spinner(
387
+ f"Counting {object_type} objects..."
388
+ ):
389
+ result = service.count_objects(ontology_rid, object_type, branch=branch)
390
+
391
+ formatter.format_dict(result, format=format, output=output)
392
+
393
+ if output:
394
+ formatter.print_success(f"Count result saved to {output}")
395
+
396
+ except (ProfileNotFoundError, MissingCredentialsError) as e:
397
+ formatter.print_error(f"Authentication error: {e}")
398
+ raise typer.Exit(1)
399
+ except Exception as e:
400
+ formatter.print_error(f"Failed to count objects: {e}")
401
+ raise typer.Exit(1)
402
+
403
+
404
+ @app.command("object-search")
405
+ def search_objects(
406
+ ontology_rid: str = typer.Argument(..., help="Ontology Resource Identifier"),
407
+ object_type: str = typer.Argument(..., help="Object type API name"),
408
+ query: str = typer.Option(..., "--query", "-q", help="Search query string"),
409
+ profile: Optional[str] = typer.Option(None, "--profile", "-p", help="Profile name"),
410
+ format: str = typer.Option(
411
+ "table", "--format", "-f", help="Output format (table, json, csv)"
412
+ ),
413
+ output: Optional[str] = typer.Option(
414
+ None, "--output", "-o", help="Output file path"
415
+ ),
416
+ page_size: Optional[int] = typer.Option(
417
+ None, "--page-size", help="Number of results per page"
418
+ ),
419
+ properties: Optional[str] = typer.Option(
420
+ None, "--properties", help="Comma-separated list of properties to include"
421
+ ),
422
+ branch: Optional[str] = typer.Option(None, "--branch", "-b", help="Branch name"),
423
+ ):
424
+ """Search objects by query."""
425
+ try:
426
+ service = OntologyObjectService(profile=profile)
427
+
428
+ prop_list = properties.split(",") if properties else None
429
+
430
+ with SpinnerProgressTracker().track_spinner(
431
+ f"Searching {object_type} objects..."
432
+ ):
433
+ objects = service.search_objects(
434
+ ontology_rid,
435
+ object_type,
436
+ query,
437
+ page_size=page_size,
438
+ properties=prop_list,
439
+ branch=branch,
440
+ )
441
+
442
+ if format == "table" and objects:
443
+ # Use first object's keys as columns
444
+ columns = list(objects[0].keys()) if objects else []
445
+ formatter.format_table(
446
+ objects, columns=columns, format=format, output=output
447
+ )
448
+ else:
449
+ formatter.format_list(objects, format=format, output=output)
450
+
451
+ if output:
452
+ formatter.print_success(f"Search results saved to {output}")
453
+
454
+ except (ProfileNotFoundError, MissingCredentialsError) as e:
455
+ formatter.print_error(f"Authentication error: {e}")
456
+ raise typer.Exit(1)
457
+ except Exception as e:
458
+ formatter.print_error(f"Failed to search objects: {e}")
459
+ raise typer.Exit(1)
460
+
461
+
369
462
  # Action commands
370
463
  @app.command("action-apply")
371
464
  def apply_action(
@@ -92,7 +92,46 @@ def create_build(
92
92
  "table", "--format", "-f", help="Output format (table, json, csv)"
93
93
  ),
94
94
  ):
95
- """Create a new build."""
95
+ """
96
+ Create a new build with specified target datasets.
97
+
98
+ The target parameter must be a JSON object with a 'type' field that specifies
99
+ the build strategy. Three types are supported:
100
+
101
+ \b
102
+ 1. Manual Build - Explicitly specify datasets to build:
103
+ {
104
+ "type": "manual",
105
+ "targetRids": ["ri.foundry.main.dataset.abc123..."]
106
+ }
107
+
108
+ \b
109
+ 2. Upstream Build - Build target datasets and all their upstream dependencies:
110
+ {
111
+ "type": "upstream",
112
+ "targetRids": ["ri.foundry.main.dataset.abc123..."],
113
+ "ignoredRids": []
114
+ }
115
+
116
+ \b
117
+ 3. Connecting Build - Build datasets between input and target datasets:
118
+ {
119
+ "type": "connecting",
120
+ "inputRids": ["ri.foundry.main.dataset.input123..."],
121
+ "targetRids": ["ri.foundry.main.dataset.target123..."],
122
+ "ignoredRids": []
123
+ }
124
+
125
+ Examples:
126
+
127
+ \b
128
+ # Build specific datasets manually
129
+ pltr orchestration builds create '{"type": "manual", "targetRids": ["ri.foundry.main.dataset.abc123"]}' --branch master
130
+
131
+ \b
132
+ # Build dataset and all upstream dependencies
133
+ pltr orchestration builds create '{"type": "upstream", "targetRids": ["ri.foundry.main.dataset.abc123"], "ignoredRids": []}' --branch master --force
134
+ """
96
135
  try:
97
136
  service = OrchestrationService(profile=profile)
98
137
 
@@ -79,6 +79,64 @@ def get_resource(
79
79
  raise typer.Exit(1)
80
80
 
81
81
 
82
+ @app.command("get-by-path")
83
+ def get_resource_by_path(
84
+ path: str = typer.Argument(
85
+ ...,
86
+ help="Absolute path to the resource (e.g., '/My Organization/Project/Dataset')",
87
+ ),
88
+ profile: Optional[str] = typer.Option(
89
+ None, "--profile", help="Profile name", autocompletion=complete_profile
90
+ ),
91
+ format: str = typer.Option(
92
+ "table",
93
+ "--format",
94
+ "-f",
95
+ help="Output format (table, json, csv)",
96
+ autocompletion=complete_output_format,
97
+ ),
98
+ output: Optional[str] = typer.Option(
99
+ None, "--output", "-o", help="Output file path"
100
+ ),
101
+ ):
102
+ """Get detailed information about a specific resource by its path."""
103
+ try:
104
+ service = ResourceService(profile=profile)
105
+
106
+ with SpinnerProgressTracker().track_spinner(
107
+ f"Fetching resource at path '{path}'..."
108
+ ):
109
+ resource = service.get_resource_by_path(path)
110
+
111
+ # Cache the RID for future completions
112
+ if resource.get("rid"):
113
+ cache_rid(resource["rid"])
114
+
115
+ # Format output
116
+ if format == "json":
117
+ if output:
118
+ formatter.save_to_file(resource, output, "json")
119
+ else:
120
+ formatter.format_dict(resource)
121
+ elif format == "csv":
122
+ if output:
123
+ formatter.save_to_file([resource], output, "csv")
124
+ else:
125
+ formatter.format_list([resource])
126
+ else:
127
+ _format_resource_table(resource)
128
+
129
+ if output:
130
+ formatter.print_success(f"Resource information saved to {output}")
131
+
132
+ except (ProfileNotFoundError, MissingCredentialsError) as e:
133
+ formatter.print_error(f"Authentication error: {e}")
134
+ raise typer.Exit(1)
135
+ except Exception as e:
136
+ formatter.print_error(f"Failed to get resource: {e}")
137
+ raise typer.Exit(1)
138
+
139
+
82
140
  @app.command("list")
83
141
  def list_resources(
84
142
  folder_rid: Optional[str] = typer.Option(
@@ -469,12 +527,15 @@ def main():
469
527
 
470
528
  Manage resources in the Foundry filesystem. Get resource information,
471
529
  search resources, manage metadata, and perform operations using Resource
472
- Identifiers (RIDs).
530
+ Identifiers (RIDs) or paths.
473
531
 
474
532
  Examples:
475
- # Get resource information
533
+ # Get resource information by RID
476
534
  pltr resource get ri.compass.main.dataset.xyz123
477
535
 
536
+ # Get resource information by path
537
+ pltr resource get-by-path "/My Organization/Project/Dataset Name"
538
+
478
539
  # List all resources
479
540
  pltr resource list
480
541
 
@@ -487,18 +487,15 @@ class DatasetService(BaseService):
487
487
  # Ensure output directory exists
488
488
  output_path.parent.mkdir(parents=True, exist_ok=True)
489
489
 
490
- file_content = self.service.Dataset.File.read(
490
+ # Use Dataset.File.content() which returns bytes directly
491
+ # Note: In SDK v1.27.0, the method is 'content' not 'read'
492
+ file_content = self.service.Dataset.File.content(
491
493
  dataset_rid=dataset_rid, file_path=file_path, branch_name=branch
492
494
  )
493
495
 
494
- # Write file content to disk
496
+ # Write file content to disk (file_content is bytes)
495
497
  with open(output_path, "wb") as f:
496
- if hasattr(file_content, "read"):
497
- # If it's a stream
498
- f.write(file_content.read())
499
- else:
500
- # If it's bytes
501
- f.write(file_content)
498
+ f.write(file_content)
502
499
 
503
500
  return {
504
501
  "dataset_rid": dataset_rid,
@@ -287,6 +287,77 @@ class OntologyObjectService(BaseService):
287
287
  except Exception as e:
288
288
  raise RuntimeError(f"Failed to list linked objects: {e}")
289
289
 
290
+ def count_objects(
291
+ self,
292
+ ontology_rid: str,
293
+ object_type: str,
294
+ branch: Optional[str] = None,
295
+ ) -> Dict[str, Any]:
296
+ """
297
+ Count objects of a specific type.
298
+
299
+ Args:
300
+ ontology_rid: Ontology Resource Identifier
301
+ object_type: Object type API name
302
+ branch: Branch name (optional)
303
+
304
+ Returns:
305
+ Dictionary containing count information
306
+ """
307
+ try:
308
+ count = self.service.OntologyObject.count(
309
+ ontology_rid,
310
+ object_type,
311
+ branch_name=branch,
312
+ )
313
+ return {
314
+ "ontology_rid": ontology_rid,
315
+ "object_type": object_type,
316
+ "count": count,
317
+ "branch": branch,
318
+ }
319
+ except Exception as e:
320
+ raise RuntimeError(f"Failed to count objects: {e}")
321
+
322
+ def search_objects(
323
+ self,
324
+ ontology_rid: str,
325
+ object_type: str,
326
+ query: str,
327
+ page_size: Optional[int] = None,
328
+ properties: Optional[List[str]] = None,
329
+ branch: Optional[str] = None,
330
+ ) -> List[Dict[str, Any]]:
331
+ """
332
+ Search objects by query.
333
+
334
+ Args:
335
+ ontology_rid: Ontology Resource Identifier
336
+ object_type: Object type API name
337
+ query: Search query string
338
+ page_size: Number of results per page
339
+ properties: List of properties to include
340
+ branch: Branch name (optional)
341
+
342
+ Returns:
343
+ List of matching object dictionaries
344
+ """
345
+ try:
346
+ result = self.service.OntologyObject.search(
347
+ ontology_rid,
348
+ object_type,
349
+ query=query,
350
+ page_size=page_size,
351
+ properties=properties,
352
+ branch_name=branch,
353
+ )
354
+ objects = []
355
+ for obj in result:
356
+ objects.append(self._format_object(obj))
357
+ return objects
358
+ except Exception as e:
359
+ raise RuntimeError(f"Failed to search objects: {e}")
360
+
290
361
  def _format_object(self, obj: Any) -> Dict[str, Any]:
291
362
  """Format object for consistent output."""
292
363
  # Objects may have various properties - extract them dynamically
@@ -30,6 +30,22 @@ class ResourceService(BaseService):
30
30
  except Exception as e:
31
31
  raise RuntimeError(f"Failed to get resource {resource_rid}: {e}")
32
32
 
33
+ def get_resource_by_path(self, path: str) -> Dict[str, Any]:
34
+ """
35
+ Get information about a specific resource by its path.
36
+
37
+ Args:
38
+ path: Absolute path to the resource (e.g., "/My Organization/Project/Dataset")
39
+
40
+ Returns:
41
+ Resource information dictionary
42
+ """
43
+ try:
44
+ resource = self.service.Resource.get_by_path(path=path, preview=True)
45
+ return self._format_resource_info(resource)
46
+ except Exception as e:
47
+ raise RuntimeError(f"Failed to get resource at path '{path}': {e}")
48
+
33
49
  def list_resources(
34
50
  self,
35
51
  folder_rid: Optional[str] = None,
@@ -64,7 +64,8 @@ class OutputFormatter:
64
64
  f.write(json_str)
65
65
  return None
66
66
  else:
67
- rich_print(json_str)
67
+ # Use plain print to ensure valid JSON output without ANSI codes
68
+ print(json_str)
68
69
  return json_str
69
70
 
70
71
  def _format_csv(
@@ -141,7 +142,11 @@ class OutputFormatter:
141
142
 
142
143
  # Add columns to table
143
144
  for column in columns:
144
- table.add_column(column, overflow="fold")
145
+ # Don't truncate RID columns - they need full visibility
146
+ if "rid" in column.lower():
147
+ table.add_column(column, no_wrap=True, overflow="fold")
148
+ else:
149
+ table.add_column(column, overflow="fold")
145
150
 
146
151
  # Add rows
147
152
  for item in data:
@@ -424,7 +429,8 @@ class OutputFormatter:
424
429
  else:
425
430
  # For simple values, just print them
426
431
  if format_type == "json":
427
- rich_print(json.dumps(data, indent=2, default=str))
432
+ # Use plain print to ensure valid JSON output without ANSI codes
433
+ print(json.dumps(data, indent=2, default=str))
428
434
  else:
429
435
  rich_print(str(data))
430
436
 
@@ -220,6 +220,111 @@ def test_list_linked_objects_command(mock_services):
220
220
  mock_instance.list_linked_objects.assert_called_once()
221
221
 
222
222
 
223
+ def test_count_objects_command(mock_services):
224
+ """Test count objects command."""
225
+ mock_instance = Mock()
226
+ mock_instance.count_objects.return_value = {
227
+ "ontology_rid": "ri.ontology.main.ontology.test",
228
+ "object_type": "Employee",
229
+ "count": 42,
230
+ "branch": None,
231
+ }
232
+ mock_services["object"].return_value = mock_instance
233
+
234
+ result = runner.invoke(
235
+ app, ["object-count", "ri.ontology.main.ontology.test", "Employee"]
236
+ )
237
+
238
+ assert result.exit_code == 0
239
+ mock_instance.count_objects.assert_called_once_with(
240
+ "ri.ontology.main.ontology.test", "Employee", branch=None
241
+ )
242
+
243
+
244
+ def test_count_objects_with_branch(mock_services):
245
+ """Test count objects with branch specified."""
246
+ mock_instance = Mock()
247
+ mock_instance.count_objects.return_value = {
248
+ "ontology_rid": "ri.ontology.main.ontology.test",
249
+ "object_type": "Employee",
250
+ "count": 24,
251
+ "branch": "master",
252
+ }
253
+ mock_services["object"].return_value = mock_instance
254
+
255
+ result = runner.invoke(
256
+ app,
257
+ [
258
+ "object-count",
259
+ "ri.ontology.main.ontology.test",
260
+ "Employee",
261
+ "--branch",
262
+ "master",
263
+ ],
264
+ )
265
+
266
+ assert result.exit_code == 0
267
+ mock_instance.count_objects.assert_called_once_with(
268
+ "ri.ontology.main.ontology.test", "Employee", branch="master"
269
+ )
270
+
271
+
272
+ def test_search_objects_command(mock_services):
273
+ """Test search objects command."""
274
+ mock_instance = Mock()
275
+ mock_instance.search_objects.return_value = [
276
+ {"employee_id": "EMP001", "name": "John Doe"}
277
+ ]
278
+ mock_services["object"].return_value = mock_instance
279
+
280
+ result = runner.invoke(
281
+ app,
282
+ [
283
+ "object-search",
284
+ "ri.ontology.main.ontology.test",
285
+ "Employee",
286
+ "--query",
287
+ "John",
288
+ ],
289
+ )
290
+
291
+ assert result.exit_code == 0
292
+ mock_instance.search_objects.assert_called_once()
293
+
294
+
295
+ def test_search_objects_with_options(mock_services):
296
+ """Test search objects with all options."""
297
+ mock_instance = Mock()
298
+ mock_instance.search_objects.return_value = [
299
+ {"employee_id": "EMP001", "name": "John Doe"}
300
+ ]
301
+ mock_services["object"].return_value = mock_instance
302
+
303
+ result = runner.invoke(
304
+ app,
305
+ [
306
+ "object-search",
307
+ "ri.ontology.main.ontology.test",
308
+ "Employee",
309
+ "--query",
310
+ "Jane",
311
+ "--page-size",
312
+ "10",
313
+ "--properties",
314
+ "name,department",
315
+ "--branch",
316
+ "master",
317
+ ],
318
+ )
319
+
320
+ assert result.exit_code == 0
321
+ call_args = mock_instance.search_objects.call_args
322
+ assert call_args[0] == ("ri.ontology.main.ontology.test", "Employee", "Jane")
323
+ assert call_args[1]["page_size"] == 10
324
+ assert call_args[1]["properties"] == ["name", "department"]
325
+ assert call_args[1]["branch"] == "master"
326
+
327
+
223
328
  # Action command tests
224
329
  def test_apply_action_command(mock_services):
225
330
  """Test apply action command."""
@@ -335,6 +335,85 @@ def test_list_linked_objects(mock_ontology_object_service, sample_object):
335
335
  mock_ontology_object_class.list_linked_objects.assert_called_once()
336
336
 
337
337
 
338
+ def test_count_objects(mock_ontology_object_service):
339
+ """Test counting objects."""
340
+ service, mock_ontology_object_class = mock_ontology_object_service
341
+ mock_ontology_object_class.count.return_value = 42
342
+
343
+ result = service.count_objects("ri.ontology.main.ontology.test", "Employee")
344
+
345
+ assert result["count"] == 42
346
+ assert result["ontology_rid"] == "ri.ontology.main.ontology.test"
347
+ assert result["object_type"] == "Employee"
348
+ assert result["branch"] is None
349
+ mock_ontology_object_class.count.assert_called_once_with(
350
+ "ri.ontology.main.ontology.test", "Employee", branch_name=None
351
+ )
352
+
353
+
354
+ def test_count_objects_with_branch(mock_ontology_object_service):
355
+ """Test counting objects with branch specified."""
356
+ service, mock_ontology_object_class = mock_ontology_object_service
357
+ mock_ontology_object_class.count.return_value = 24
358
+
359
+ result = service.count_objects(
360
+ "ri.ontology.main.ontology.test", "Employee", branch="master"
361
+ )
362
+
363
+ assert result["count"] == 24
364
+ assert result["branch"] == "master"
365
+ mock_ontology_object_class.count.assert_called_once_with(
366
+ "ri.ontology.main.ontology.test", "Employee", branch_name="master"
367
+ )
368
+
369
+
370
+ def test_search_objects(mock_ontology_object_service, sample_object):
371
+ """Test searching objects."""
372
+ service, mock_ontology_object_class = mock_ontology_object_service
373
+ mock_ontology_object_class.search.return_value = [sample_object]
374
+
375
+ result = service.search_objects(
376
+ "ri.ontology.main.ontology.test", "Employee", "John"
377
+ )
378
+
379
+ assert len(result) == 1
380
+ assert result[0]["employee_id"] == "EMP001"
381
+ assert result[0]["name"] == "John Doe"
382
+ mock_ontology_object_class.search.assert_called_once_with(
383
+ "ri.ontology.main.ontology.test",
384
+ "Employee",
385
+ query="John",
386
+ page_size=None,
387
+ properties=None,
388
+ branch_name=None,
389
+ )
390
+
391
+
392
+ def test_search_objects_with_options(mock_ontology_object_service, sample_object):
393
+ """Test searching objects with all options."""
394
+ service, mock_ontology_object_class = mock_ontology_object_service
395
+ mock_ontology_object_class.search.return_value = [sample_object]
396
+
397
+ result = service.search_objects(
398
+ "ri.ontology.main.ontology.test",
399
+ "Employee",
400
+ "Jane",
401
+ page_size=10,
402
+ properties=["name", "department"],
403
+ branch="master",
404
+ )
405
+
406
+ assert len(result) == 1
407
+ mock_ontology_object_class.search.assert_called_once_with(
408
+ "ri.ontology.main.ontology.test",
409
+ "Employee",
410
+ query="Jane",
411
+ page_size=10,
412
+ properties=["name", "department"],
413
+ branch_name="master",
414
+ )
415
+
416
+
338
417
  # ActionService Tests
339
418
  def test_apply_action(mock_action_service, sample_action_result):
340
419
  """Test applying an action."""
@@ -66,6 +66,42 @@ class TestResourceService:
66
66
  ):
67
67
  resource_service.get_resource("ri.compass.main.dataset.123")
68
68
 
69
+ def test_get_resource_by_path(self, resource_service, mock_client):
70
+ """Test getting a resource by path."""
71
+ mock_resource = Mock()
72
+ mock_resource.rid = "ri.compass.main.dataset.123"
73
+ mock_resource.display_name = "Test Dataset"
74
+ mock_resource.type = "dataset"
75
+ mock_resource.path = "/My Organization/Project/Test Dataset"
76
+
77
+ mock_client.filesystem.Resource.get_by_path.return_value = mock_resource
78
+ resource_service._client = mock_client
79
+
80
+ result = resource_service.get_resource_by_path(
81
+ "/My Organization/Project/Test Dataset"
82
+ )
83
+
84
+ mock_client.filesystem.Resource.get_by_path.assert_called_once_with(
85
+ path="/My Organization/Project/Test Dataset", preview=True
86
+ )
87
+ assert result["rid"] == "ri.compass.main.dataset.123"
88
+ assert result["display_name"] == "Test Dataset"
89
+ assert result["type"] == "dataset"
90
+ assert result["path"] == "/My Organization/Project/Test Dataset"
91
+
92
+ def test_get_resource_by_path_failure(self, resource_service, mock_client):
93
+ """Test handling resource get by path failure."""
94
+ mock_client.filesystem.Resource.get_by_path.side_effect = Exception(
95
+ "Path not found"
96
+ )
97
+ resource_service._client = mock_client
98
+
99
+ with pytest.raises(
100
+ RuntimeError,
101
+ match="Failed to get resource at path '/Invalid/Path': Path not found",
102
+ ):
103
+ resource_service.get_resource_by_path("/Invalid/Path")
104
+
69
105
  def test_list_resources(self, resource_service, mock_client):
70
106
  """Test listing resources."""
71
107
  mock_resources = [Mock(), Mock()]
@@ -710,7 +710,7 @@ wheels = [
710
710
 
711
711
  [[package]]
712
712
  name = "pltr-cli"
713
- version = "0.9.3"
713
+ version = "0.11.0"
714
714
  source = { editable = "." }
715
715
  dependencies = [
716
716
  { name = "click-repl" },
@@ -1 +0,0 @@
1
- __version__ = "0.9.3"
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes