pltr-cli 0.10.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.
@@ -487,18 +512,15 @@ class DatasetService(BaseService):
487
512
  # Ensure output directory exists
488
513
  output_path.parent.mkdir(parents=True, exist_ok=True)
489
514
 
490
- file_content = self.service.Dataset.File.read(
515
+ # Use Dataset.File.content() which returns bytes directly
516
+ # Note: In SDK v1.27.0, the method is 'content' not 'read'
517
+ file_content = self.service.Dataset.File.content(
491
518
  dataset_rid=dataset_rid, file_path=file_path, branch_name=branch
492
519
  )
493
520
 
494
- # Write file content to disk
521
+ # Write file content to disk (file_content is bytes)
495
522
  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)
523
+ f.write(file_content)
502
524
 
503
525
  return {
504
526
  "dataset_rid": dataset_rid,
@@ -519,6 +541,8 @@ class DatasetService(BaseService):
519
541
  """
520
542
  List files in a dataset.
521
543
 
544
+ DEPRECATED: Use list_files_paginated() instead for better pagination support.
545
+
522
546
  Args:
523
547
  dataset_rid: Dataset Resource Identifier
524
548
  branch: Dataset branch name
@@ -543,6 +567,53 @@ class DatasetService(BaseService):
543
567
  except Exception as e:
544
568
  raise RuntimeError(f"Failed to list files in dataset {dataset_rid}: {e}")
545
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
+
546
617
  def get_branches(self, dataset_rid: str) -> List[Dict[str, Any]]:
547
618
  """
548
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,
@@ -287,6 +334,77 @@ class OntologyObjectService(BaseService):
287
334
  except Exception as e:
288
335
  raise RuntimeError(f"Failed to list linked objects: {e}")
289
336
 
337
+ def count_objects(
338
+ self,
339
+ ontology_rid: str,
340
+ object_type: str,
341
+ branch: Optional[str] = None,
342
+ ) -> Dict[str, Any]:
343
+ """
344
+ Count objects of a specific type.
345
+
346
+ Args:
347
+ ontology_rid: Ontology Resource Identifier
348
+ object_type: Object type API name
349
+ branch: Branch name (optional)
350
+
351
+ Returns:
352
+ Dictionary containing count information
353
+ """
354
+ try:
355
+ count = self.service.OntologyObject.count(
356
+ ontology_rid,
357
+ object_type,
358
+ branch_name=branch,
359
+ )
360
+ return {
361
+ "ontology_rid": ontology_rid,
362
+ "object_type": object_type,
363
+ "count": count,
364
+ "branch": branch,
365
+ }
366
+ except Exception as e:
367
+ raise RuntimeError(f"Failed to count objects: {e}")
368
+
369
+ def search_objects(
370
+ self,
371
+ ontology_rid: str,
372
+ object_type: str,
373
+ query: str,
374
+ page_size: Optional[int] = None,
375
+ properties: Optional[List[str]] = None,
376
+ branch: Optional[str] = None,
377
+ ) -> List[Dict[str, Any]]:
378
+ """
379
+ Search objects by query.
380
+
381
+ Args:
382
+ ontology_rid: Ontology Resource Identifier
383
+ object_type: Object type API name
384
+ query: Search query string
385
+ page_size: Number of results per page
386
+ properties: List of properties to include
387
+ branch: Branch name (optional)
388
+
389
+ Returns:
390
+ List of matching object dictionaries
391
+ """
392
+ try:
393
+ result = self.service.OntologyObject.search(
394
+ ontology_rid,
395
+ object_type,
396
+ query=query,
397
+ page_size=page_size,
398
+ properties=properties,
399
+ branch_name=branch,
400
+ )
401
+ objects = []
402
+ for obj in result:
403
+ objects.append(self._format_object(obj))
404
+ return objects
405
+ except Exception as e:
406
+ raise RuntimeError(f"Failed to search objects: {e}")
407
+
290
408
  def _format_object(self, obj: Any) -> Dict[str, Any]:
291
409
  """Format object for consistent output."""
292
410
  # Objects may have various properties - extract them dynamically
@@ -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