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/services/dataset.py CHANGED
@@ -2,10 +2,12 @@
2
2
  Dataset service wrapper for Foundry SDK.
3
3
  """
4
4
 
5
- from typing import Any, Optional, List, Dict, Union
5
+ from typing import Any, Callable, Dict, List, Optional, Union
6
6
  from pathlib import Path
7
7
  import csv
8
8
 
9
+ from ..config.settings import Settings
10
+ from ..utils.pagination import PaginationConfig, PaginationResult
9
11
  from .base import BaseService
10
12
 
11
13
 
@@ -312,6 +314,29 @@ class DatasetService(BaseService):
312
314
  except Exception as e:
313
315
  raise RuntimeError(f"Failed to read dataset {dataset_rid}: {e}")
314
316
 
317
+ def preview_data(
318
+ self,
319
+ dataset_rid: str,
320
+ limit: int = 10,
321
+ ) -> List[Dict[str, Any]]:
322
+ """
323
+ Preview dataset contents as a list of records.
324
+
325
+ Args:
326
+ dataset_rid: Dataset Resource Identifier
327
+ limit: Maximum number of rows to return
328
+
329
+ Returns:
330
+ List of dictionaries representing rows
331
+ """
332
+ try:
333
+ # Use read_table with pandas format for easy conversion
334
+ df = self.read_table(dataset_rid, format="pandas")
335
+ # Limit rows and convert to records
336
+ return df.head(limit).to_dict(orient="records")
337
+ except Exception as e:
338
+ raise RuntimeError(f"Failed to preview dataset {dataset_rid}: {e}")
339
+
315
340
  def delete_dataset(self, dataset_rid: str) -> bool:
316
341
  """
317
342
  Delete a dataset.
@@ -516,6 +541,8 @@ class DatasetService(BaseService):
516
541
  """
517
542
  List files in a dataset.
518
543
 
544
+ DEPRECATED: Use list_files_paginated() instead for better pagination support.
545
+
519
546
  Args:
520
547
  dataset_rid: Dataset Resource Identifier
521
548
  branch: Dataset branch name
@@ -540,6 +567,53 @@ class DatasetService(BaseService):
540
567
  except Exception as e:
541
568
  raise RuntimeError(f"Failed to list files in dataset {dataset_rid}: {e}")
542
569
 
570
+ def list_files_paginated(
571
+ self,
572
+ dataset_rid: str,
573
+ branch: str,
574
+ config: PaginationConfig,
575
+ progress_callback: Optional[Callable[[int, int], None]] = None,
576
+ ) -> PaginationResult:
577
+ """
578
+ List files with full pagination control.
579
+
580
+ Args:
581
+ dataset_rid: Dataset Resource Identifier
582
+ branch: Dataset branch name
583
+ config: Pagination configuration
584
+ progress_callback: Optional progress callback
585
+
586
+ Returns:
587
+ PaginationResult with file information and metadata
588
+ """
589
+ try:
590
+ settings = Settings()
591
+
592
+ # Get iterator from SDK - ResourceIterator with next_page_token support
593
+ iterator = self.service.Dataset.File.list(
594
+ dataset_rid=dataset_rid,
595
+ branch_name=branch,
596
+ page_size=config.page_size or settings.get("page_size", 20),
597
+ )
598
+
599
+ # Use iterator pagination handler
600
+ result = self._paginate_iterator(iterator, config, progress_callback)
601
+
602
+ # Format file information
603
+ result.data = [
604
+ {
605
+ "path": file.path,
606
+ "size_bytes": getattr(file, "size_bytes", None),
607
+ "last_modified": getattr(file, "last_modified", None),
608
+ "transaction_rid": getattr(file, "transaction_rid", None),
609
+ }
610
+ for file in result.data
611
+ ]
612
+
613
+ return result
614
+ except Exception as e:
615
+ raise RuntimeError(f"Failed to list files: {e}")
616
+
543
617
  def get_branches(self, dataset_rid: str) -> List[Dict[str, Any]]:
544
618
  """
545
619
  Get list of branches for a dataset.
@@ -226,15 +226,7 @@ class MediaSetsService(BaseService):
226
226
  preview=preview,
227
227
  )
228
228
 
229
- with open(output_path_obj, "wb") as file:
230
- if hasattr(response, "content"):
231
- file.write(response.content)
232
- else:
233
- # Handle streaming response
234
- for chunk in response:
235
- file.write(chunk)
236
-
237
- file_size = output_path_obj.stat().st_size
229
+ file_size = self._write_response_to_file(response, output_path_obj)
238
230
  return {
239
231
  "media_set_rid": media_set_rid,
240
232
  "media_item_rid": media_item_rid,
@@ -291,3 +283,146 @@ class MediaSetsService(BaseService):
291
283
  "url": getattr(reference_response, "url", "unknown"),
292
284
  "expires_at": getattr(reference_response, "expires_at", None),
293
285
  }
286
+
287
+ def _write_response_to_file(self, response: Any, output_path: Path) -> int:
288
+ """
289
+ Write response content to file and return file size.
290
+
291
+ Args:
292
+ response: SDK response object (with .content attribute or iterable)
293
+ output_path: Path object for the output file
294
+
295
+ Returns:
296
+ File size in bytes
297
+ """
298
+ with open(output_path, "wb") as file:
299
+ if hasattr(response, "content"):
300
+ file.write(response.content)
301
+ else:
302
+ # Handle streaming response
303
+ for chunk in response:
304
+ file.write(chunk)
305
+ return output_path.stat().st_size
306
+
307
+ def calculate_thumbnail(
308
+ self,
309
+ media_set_rid: str,
310
+ media_item_rid: str,
311
+ preview: bool = False,
312
+ ) -> Dict[str, Any]:
313
+ """
314
+ Initiate thumbnail generation for an image.
315
+
316
+ Args:
317
+ media_set_rid: Media Set Resource Identifier
318
+ media_item_rid: Media Item Resource Identifier
319
+ preview: Enable preview mode
320
+
321
+ Returns:
322
+ Thumbnail calculation status information
323
+ """
324
+ try:
325
+ response = self.service.MediaSet.calculate(
326
+ media_set_rid=media_set_rid,
327
+ media_item_rid=media_item_rid,
328
+ preview=preview,
329
+ )
330
+ return self._format_thumbnail_status(response)
331
+ except Exception as e:
332
+ raise RuntimeError(f"Failed to calculate thumbnail: {e}")
333
+
334
+ def retrieve_thumbnail(
335
+ self,
336
+ media_set_rid: str,
337
+ media_item_rid: str,
338
+ output_path: str,
339
+ preview: bool = False,
340
+ ) -> Dict[str, Any]:
341
+ """
342
+ Retrieve a calculated thumbnail (200px wide webp).
343
+
344
+ Args:
345
+ media_set_rid: Media Set Resource Identifier
346
+ media_item_rid: Media Item Resource Identifier
347
+ output_path: Local path where thumbnail should be saved
348
+ preview: Enable preview mode
349
+
350
+ Returns:
351
+ Download response information
352
+ """
353
+ try:
354
+ output_path_obj = Path(output_path)
355
+ output_path_obj.parent.mkdir(parents=True, exist_ok=True)
356
+
357
+ response = self.service.MediaSet.retrieve(
358
+ media_set_rid=media_set_rid,
359
+ media_item_rid=media_item_rid,
360
+ preview=preview,
361
+ )
362
+
363
+ file_size = self._write_response_to_file(response, output_path_obj)
364
+
365
+ # Validate that we received actual content
366
+ if file_size == 0:
367
+ output_path_obj.unlink(missing_ok=True)
368
+ raise RuntimeError(
369
+ "Downloaded thumbnail is empty - thumbnail may not be ready yet"
370
+ )
371
+
372
+ return {
373
+ "media_set_rid": media_set_rid,
374
+ "media_item_rid": media_item_rid,
375
+ "output_path": str(output_path_obj),
376
+ "file_size": file_size,
377
+ "downloaded": True,
378
+ "format": "image/webp",
379
+ }
380
+ except Exception as e:
381
+ raise RuntimeError(f"Failed to retrieve thumbnail: {e}")
382
+
383
+ def upload_temp_media(
384
+ self,
385
+ file_path: str,
386
+ filename: Optional[str] = None,
387
+ attribution: Optional[str] = None,
388
+ preview: bool = False,
389
+ ) -> Dict[str, Any]:
390
+ """
391
+ Upload temporary media (auto-deleted after 1 hour if not persisted).
392
+
393
+ Args:
394
+ file_path: Local path to the file to upload
395
+ filename: Optional filename override
396
+ attribution: Optional attribution string
397
+ preview: Enable preview mode
398
+
399
+ Returns:
400
+ Media reference information
401
+ """
402
+ try:
403
+ file_path_obj = Path(file_path)
404
+ if not file_path_obj.exists():
405
+ raise FileNotFoundError(f"File not found: {file_path}")
406
+
407
+ # Use provided filename or default to file name
408
+ upload_filename = filename or file_path_obj.name
409
+
410
+ with open(file_path_obj, "rb") as file:
411
+ response = self.service.MediaSet.upload_media(
412
+ body=file,
413
+ filename=upload_filename,
414
+ attribution=attribution,
415
+ preview=preview,
416
+ )
417
+
418
+ return self._format_media_reference(response)
419
+ except Exception as e:
420
+ raise RuntimeError(f"Failed to upload temporary media: {e}")
421
+
422
+ def _format_thumbnail_status(self, status_response: Any) -> Dict[str, Any]:
423
+ """Format thumbnail calculation status response for display."""
424
+ return {
425
+ "status": getattr(status_response, "status", "unknown"),
426
+ "transformation_id": getattr(status_response, "transformation_id", None),
427
+ "media_item_rid": getattr(status_response, "media_item_rid", None),
428
+ }
pltr/services/ontology.py CHANGED
@@ -2,7 +2,10 @@
2
2
  Ontology service wrappers for Foundry SDK.
3
3
  """
4
4
 
5
- from typing import Any, Optional, Dict, List
5
+ from typing import Any, Callable, Dict, List, Optional
6
+
7
+ from ..config.settings import Settings
8
+ from ..utils.pagination import PaginationConfig, PaginationResult
6
9
  from .base import BaseService
7
10
 
8
11
 
@@ -165,6 +168,8 @@ class OntologyObjectService(BaseService):
165
168
  """
166
169
  List objects of a specific type.
167
170
 
171
+ DEPRECATED: Use list_objects_paginated() instead for better pagination support.
172
+
168
173
  Args:
169
174
  ontology_rid: Ontology Resource Identifier
170
175
  object_type: Object type API name
@@ -188,6 +193,48 @@ class OntologyObjectService(BaseService):
188
193
  except Exception as e:
189
194
  raise RuntimeError(f"Failed to list objects: {e}")
190
195
 
196
+ def list_objects_paginated(
197
+ self,
198
+ ontology_rid: str,
199
+ object_type: str,
200
+ config: PaginationConfig,
201
+ properties: Optional[List[str]] = None,
202
+ progress_callback: Optional[Callable[[int, int], None]] = None,
203
+ ) -> PaginationResult:
204
+ """
205
+ List objects with full pagination control.
206
+
207
+ Args:
208
+ ontology_rid: Ontology Resource Identifier
209
+ object_type: Object type API name
210
+ config: Pagination configuration
211
+ properties: List of properties to include
212
+ progress_callback: Optional progress callback
213
+
214
+ Returns:
215
+ PaginationResult with objects and metadata
216
+ """
217
+ try:
218
+ settings = Settings()
219
+
220
+ # Get iterator from SDK - ResourceIterator with next_page_token support
221
+ iterator = self.service.OntologyObject.list(
222
+ ontology_rid,
223
+ object_type,
224
+ page_size=config.page_size or settings.get("page_size", 20),
225
+ properties=properties,
226
+ )
227
+
228
+ # Use iterator pagination handler
229
+ result = self._paginate_iterator(iterator, config, progress_callback)
230
+
231
+ # Format objects
232
+ result.data = [self._format_object(obj) for obj in result.data]
233
+
234
+ return result
235
+ except Exception as e:
236
+ raise RuntimeError(f"Failed to list objects: {e}")
237
+
191
238
  def get_object(
192
239
  self,
193
240
  ontology_rid: str,
@@ -3,8 +3,10 @@ Orchestration service wrapper for Foundry SDK v2 API.
3
3
  Provides operations for managing builds, jobs, and schedules.
4
4
  """
5
5
 
6
- from typing import Any, Optional, Dict, List
6
+ from typing import Any, Callable, Dict, List, Optional
7
7
 
8
+ from ..config.settings import Settings
9
+ from ..utils.pagination import PaginationConfig, PaginationResult
8
10
  from .base import BaseService
9
11
 
10
12
 
@@ -134,6 +136,8 @@ class OrchestrationService(BaseService):
134
136
  """
135
137
  Search for builds.
136
138
 
139
+ DEPRECATED: Use search_builds_paginated() instead for better pagination support.
140
+
137
141
  Args:
138
142
  page_size: Number of results per page
139
143
  page_token: Token for pagination
@@ -155,6 +159,63 @@ class OrchestrationService(BaseService):
155
159
  except Exception as e:
156
160
  raise RuntimeError(f"Failed to search builds: {e}")
157
161
 
162
+ def search_builds_paginated(
163
+ self,
164
+ config: PaginationConfig,
165
+ progress_callback: Optional[Callable[[int, int], None]] = None,
166
+ **search_params,
167
+ ) -> PaginationResult:
168
+ """
169
+ Search for builds with full pagination control.
170
+
171
+ Args:
172
+ config: Pagination configuration
173
+ progress_callback: Optional progress callback
174
+ **search_params: Additional search parameters
175
+
176
+ Returns:
177
+ PaginationResult with builds and metadata
178
+ """
179
+ try:
180
+ settings = Settings()
181
+
182
+ def fetch_page(page_token: Optional[str]) -> Dict[str, Any]:
183
+ """Fetch a single page of builds."""
184
+ kwargs: Dict[str, Any] = {
185
+ "page_size": config.page_size or settings.get("page_size", 20),
186
+ }
187
+ if page_token:
188
+ kwargs["page_token"] = page_token
189
+ kwargs.update(search_params)
190
+
191
+ response = self.service.Build.search(**kwargs)
192
+ return self._format_builds_search_response(response)
193
+
194
+ return self._paginate_response(fetch_page, config, progress_callback)
195
+ except Exception as e:
196
+ raise RuntimeError(f"Failed to search builds: {e}")
197
+
198
+ def get_builds_batch(self, build_rids: List[str]) -> Dict[str, Any]:
199
+ """
200
+ Get multiple builds in batch.
201
+
202
+ Args:
203
+ build_rids: List of Build Resource Identifiers (max 100)
204
+
205
+ Returns:
206
+ Batch response with build information
207
+ """
208
+ if len(build_rids) > 100:
209
+ raise ValueError("Maximum batch size is 100 builds")
210
+
211
+ try:
212
+ # SDK expects list of {"rid": ...} objects for batch operations
213
+ body = [{"rid": rid} for rid in build_rids]
214
+ response = self.service.Build.get_batch(body)
215
+ return self._format_builds_batch_response(response)
216
+ except Exception as e:
217
+ raise RuntimeError(f"Failed to get builds batch: {e}")
218
+
158
219
  # Job operations
159
220
  def get_job(self, job_rid: str) -> Dict[str, Any]:
160
221
  """
@@ -353,6 +414,35 @@ class OrchestrationService(BaseService):
353
414
  except Exception as e:
354
415
  raise RuntimeError(f"Failed to replace schedule {schedule_rid}: {e}")
355
416
 
417
+ def get_schedule_runs(
418
+ self,
419
+ schedule_rid: str,
420
+ page_size: Optional[int] = None,
421
+ page_token: Optional[str] = None,
422
+ ) -> Dict[str, Any]:
423
+ """
424
+ Get recent execution runs for a schedule.
425
+
426
+ Args:
427
+ schedule_rid: Schedule Resource Identifier
428
+ page_size: Number of results per page
429
+ page_token: Token for pagination
430
+
431
+ Returns:
432
+ Runs list with pagination info
433
+ """
434
+ try:
435
+ kwargs: Dict[str, Any] = {"schedule_rid": schedule_rid}
436
+ if page_size is not None:
437
+ kwargs["page_size"] = page_size
438
+ if page_token is not None:
439
+ kwargs["page_token"] = page_token
440
+
441
+ response = self.service.Schedule.runs(**kwargs)
442
+ return self._format_schedule_runs_response(response)
443
+ except Exception as e:
444
+ raise RuntimeError(f"Failed to get runs for schedule {schedule_rid}: {e}")
445
+
356
446
  # Formatting methods
357
447
  def _format_build_info(self, build: Any) -> Dict[str, Any]:
358
448
  """Format build information for consistent output."""
@@ -455,3 +545,45 @@ class OrchestrationService(BaseService):
455
545
  result["jobs"].append(self._format_job_info(item.data))
456
546
 
457
547
  return result
548
+
549
+ def _format_builds_batch_response(self, response: Any) -> Dict[str, Any]:
550
+ """Format builds batch response."""
551
+ result: Dict[str, Any] = {"builds": []}
552
+
553
+ if hasattr(response, "data"):
554
+ for item in response.data:
555
+ if hasattr(item, "data"):
556
+ result["builds"].append(self._format_build_info(item.data))
557
+
558
+ return result
559
+
560
+ def _format_run_info(self, run: Any) -> Dict[str, Any]:
561
+ """Format schedule run information for consistent output."""
562
+ info = {}
563
+
564
+ for attr in [
565
+ "rid",
566
+ "schedule_rid",
567
+ "status",
568
+ "created_time",
569
+ "started_time",
570
+ "finished_time",
571
+ "build_rid",
572
+ "result",
573
+ ]:
574
+ if hasattr(run, attr):
575
+ info[attr] = getattr(run, attr)
576
+
577
+ return info
578
+
579
+ def _format_schedule_runs_response(self, response: Any) -> Dict[str, Any]:
580
+ """Format schedule runs response."""
581
+ result: Dict[str, Any] = {"runs": []}
582
+
583
+ if hasattr(response, "data"):
584
+ result["runs"] = [self._format_run_info(run) for run in response.data]
585
+
586
+ if hasattr(response, "next_page_token"):
587
+ result["next_page_token"] = response.next_page_token
588
+
589
+ return result
pltr/services/project.py CHANGED
@@ -185,6 +185,126 @@ class ProjectService(BaseService):
185
185
  except Exception as e:
186
186
  raise RuntimeError(f"Failed to get projects batch: {e}")
187
187
 
188
+ # ==================== Organization Operations ====================
189
+
190
+ def add_organizations(self, project_rid: str, organization_rids: List[str]) -> None:
191
+ """
192
+ Add organizations to a project.
193
+
194
+ Args:
195
+ project_rid: Project Resource Identifier
196
+ organization_rids: List of organization RIDs to add
197
+
198
+ Raises:
199
+ RuntimeError: If adding organizations fails
200
+ """
201
+ try:
202
+ self.service.Project.add_organizations(
203
+ project_rid, organization_rids=organization_rids, preview=True
204
+ )
205
+ except Exception as e:
206
+ raise RuntimeError(
207
+ f"Failed to add organizations to project {project_rid}: {e}"
208
+ )
209
+
210
+ def remove_organizations(
211
+ self, project_rid: str, organization_rids: List[str]
212
+ ) -> None:
213
+ """
214
+ Remove organizations from a project.
215
+
216
+ Args:
217
+ project_rid: Project Resource Identifier
218
+ organization_rids: List of organization RIDs to remove
219
+
220
+ Raises:
221
+ RuntimeError: If removing organizations fails
222
+ """
223
+ try:
224
+ self.service.Project.remove_organizations(
225
+ project_rid, organization_rids=organization_rids, preview=True
226
+ )
227
+ except Exception as e:
228
+ raise RuntimeError(
229
+ f"Failed to remove organizations from project {project_rid}: {e}"
230
+ )
231
+
232
+ def list_organizations(
233
+ self,
234
+ project_rid: str,
235
+ page_size: Optional[int] = None,
236
+ page_token: Optional[str] = None,
237
+ ) -> List[Dict[str, Any]]:
238
+ """
239
+ List organizations directly applied to a project.
240
+
241
+ Args:
242
+ project_rid: Project Resource Identifier
243
+ page_size: Number of items per page (optional)
244
+ page_token: Pagination token (optional)
245
+
246
+ Returns:
247
+ List of organization information dictionaries
248
+ """
249
+ try:
250
+ organizations = []
251
+ list_params: Dict[str, Any] = {"preview": True}
252
+
253
+ if page_size:
254
+ list_params["page_size"] = page_size
255
+ if page_token:
256
+ list_params["page_token"] = page_token
257
+
258
+ for org in self.service.Project.organizations(project_rid, **list_params):
259
+ organizations.append(self._format_organization_info(org))
260
+ return organizations
261
+ except Exception as e:
262
+ raise RuntimeError(
263
+ f"Failed to list organizations for project {project_rid}: {e}"
264
+ )
265
+
266
+ # ==================== Template Operations ====================
267
+
268
+ def create_project_from_template(
269
+ self,
270
+ template_rid: str,
271
+ variable_values: Dict[str, str],
272
+ default_roles: Optional[List[str]] = None,
273
+ organization_rids: Optional[List[str]] = None,
274
+ project_description: Optional[str] = None,
275
+ ) -> Dict[str, Any]:
276
+ """
277
+ Create a project from a template.
278
+
279
+ Args:
280
+ template_rid: Template Resource Identifier
281
+ variable_values: Dictionary mapping template variable names to values
282
+ default_roles: List of default role names (optional)
283
+ organization_rids: List of organization RIDs (optional)
284
+ project_description: Project description (optional)
285
+
286
+ Returns:
287
+ Created project information
288
+ """
289
+ try:
290
+ create_params: Dict[str, Any] = {
291
+ "template_rid": template_rid,
292
+ "variable_values": variable_values,
293
+ "preview": True,
294
+ }
295
+
296
+ if default_roles:
297
+ create_params["default_roles"] = default_roles
298
+ if organization_rids:
299
+ create_params["organization_rids"] = organization_rids
300
+ if project_description:
301
+ create_params["project_description"] = project_description
302
+
303
+ project = self.service.Project.create_from_template(**create_params)
304
+ return self._format_project_info(project)
305
+ except Exception as e:
306
+ raise RuntimeError(f"Failed to create project from template: {e}")
307
+
188
308
  def _format_project_info(self, project: Any) -> Dict[str, Any]:
189
309
  """
190
310
  Format project information for consistent output.
@@ -213,6 +333,22 @@ class ProjectService(BaseService):
213
333
  "type": "project",
214
334
  }
215
335
 
336
+ def _format_organization_info(self, organization: Any) -> Dict[str, Any]:
337
+ """
338
+ Format organization information for consistent output.
339
+
340
+ Args:
341
+ organization: Organization object from Foundry SDK
342
+
343
+ Returns:
344
+ Formatted organization information dictionary
345
+ """
346
+ return {
347
+ "organization_rid": getattr(organization, "organization_rid", None),
348
+ "display_name": getattr(organization, "display_name", None),
349
+ "description": getattr(organization, "description", None),
350
+ }
351
+
216
352
  def _format_timestamp(self, timestamp: Any) -> Optional[str]:
217
353
  """
218
354
  Format timestamp for display.