pltr-cli 0.1.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/__init__.py +1 -0
- pltr/auth/__init__.py +1 -0
- pltr/auth/base.py +49 -0
- pltr/auth/manager.py +129 -0
- pltr/auth/oauth.py +83 -0
- pltr/auth/storage.py +87 -0
- pltr/auth/token.py +55 -0
- pltr/cli.py +54 -0
- pltr/commands/__init__.py +1 -0
- pltr/commands/configure.py +151 -0
- pltr/commands/dataset.py +98 -0
- pltr/commands/verify.py +185 -0
- pltr/config/__init__.py +1 -0
- pltr/config/profiles.py +124 -0
- pltr/config/settings.py +103 -0
- pltr/services/__init__.py +1 -0
- pltr/services/base.py +62 -0
- pltr/services/dataset.py +90 -0
- pltr/services/dataset_full.py +302 -0
- pltr/services/dataset_v2.py +128 -0
- pltr/utils/__init__.py +1 -0
- pltr/utils/formatting.py +331 -0
- pltr/utils/progress.py +328 -0
- pltr_cli-0.1.0.dist-info/METADATA +203 -0
- pltr_cli-0.1.0.dist-info/RECORD +28 -0
- pltr_cli-0.1.0.dist-info/WHEEL +4 -0
- pltr_cli-0.1.0.dist-info/entry_points.txt +2 -0
- pltr_cli-0.1.0.dist-info/licenses/LICENSE +21 -0
pltr/commands/dataset.py
ADDED
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Simplified dataset commands that work with foundry-platform-sdk v1.27.0.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import typer
|
|
6
|
+
from typing import Optional
|
|
7
|
+
from rich.console import Console
|
|
8
|
+
|
|
9
|
+
from ..services.dataset import DatasetService
|
|
10
|
+
from ..utils.formatting import OutputFormatter
|
|
11
|
+
from ..utils.progress import SpinnerProgressTracker
|
|
12
|
+
from ..auth.base import ProfileNotFoundError, MissingCredentialsError
|
|
13
|
+
|
|
14
|
+
app = typer.Typer()
|
|
15
|
+
console = Console()
|
|
16
|
+
formatter = OutputFormatter(console)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
@app.command("get")
|
|
20
|
+
def get_dataset(
|
|
21
|
+
dataset_rid: str = typer.Argument(..., help="Dataset Resource Identifier"),
|
|
22
|
+
profile: Optional[str] = typer.Option(None, "--profile", "-p", help="Profile name"),
|
|
23
|
+
format: str = typer.Option(
|
|
24
|
+
"table", "--format", "-f", help="Output format (table, json, csv)"
|
|
25
|
+
),
|
|
26
|
+
output: Optional[str] = typer.Option(
|
|
27
|
+
None, "--output", "-o", help="Output file path"
|
|
28
|
+
),
|
|
29
|
+
):
|
|
30
|
+
"""Get detailed information about a specific dataset."""
|
|
31
|
+
try:
|
|
32
|
+
service = DatasetService(profile=profile)
|
|
33
|
+
|
|
34
|
+
with SpinnerProgressTracker().track_spinner(
|
|
35
|
+
f"Fetching dataset {dataset_rid}..."
|
|
36
|
+
):
|
|
37
|
+
dataset = service.get_dataset(dataset_rid)
|
|
38
|
+
|
|
39
|
+
formatter.format_dataset_detail(dataset, format, output)
|
|
40
|
+
|
|
41
|
+
if output:
|
|
42
|
+
formatter.print_success(f"Dataset information saved to {output}")
|
|
43
|
+
|
|
44
|
+
except (ProfileNotFoundError, MissingCredentialsError) as e:
|
|
45
|
+
formatter.print_error(f"Authentication error: {e}")
|
|
46
|
+
raise typer.Exit(1)
|
|
47
|
+
except Exception as e:
|
|
48
|
+
formatter.print_error(f"Failed to get dataset: {e}")
|
|
49
|
+
raise typer.Exit(1)
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
# schema command removed - uses preview-only API that returns INVALID_ARGUMENT
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
@app.command("create")
|
|
56
|
+
def create_dataset(
|
|
57
|
+
name: str = typer.Argument(..., help="Dataset name"),
|
|
58
|
+
profile: Optional[str] = typer.Option(None, "--profile", "-p", help="Profile name"),
|
|
59
|
+
parent_folder: Optional[str] = typer.Option(
|
|
60
|
+
None, "--parent-folder", help="Parent folder RID"
|
|
61
|
+
),
|
|
62
|
+
format: str = typer.Option(
|
|
63
|
+
"table", "--format", "-f", help="Output format (table, json, csv)"
|
|
64
|
+
),
|
|
65
|
+
):
|
|
66
|
+
"""Create a new dataset."""
|
|
67
|
+
try:
|
|
68
|
+
service = DatasetService(profile=profile)
|
|
69
|
+
|
|
70
|
+
with SpinnerProgressTracker().track_spinner(f"Creating dataset '{name}'..."):
|
|
71
|
+
dataset = service.create_dataset(name=name, parent_folder_rid=parent_folder)
|
|
72
|
+
|
|
73
|
+
formatter.print_success(f"Successfully created dataset '{name}'")
|
|
74
|
+
formatter.print_info(f"Dataset RID: {dataset.get('rid', 'unknown')}")
|
|
75
|
+
|
|
76
|
+
# Show dataset details
|
|
77
|
+
formatter.format_dataset_detail(dataset, format)
|
|
78
|
+
|
|
79
|
+
except (ProfileNotFoundError, MissingCredentialsError) as e:
|
|
80
|
+
formatter.print_error(f"Authentication error: {e}")
|
|
81
|
+
raise typer.Exit(1)
|
|
82
|
+
except Exception as e:
|
|
83
|
+
formatter.print_error(f"Failed to create dataset: {e}")
|
|
84
|
+
raise typer.Exit(1)
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
@app.callback()
|
|
88
|
+
def main():
|
|
89
|
+
"""
|
|
90
|
+
Dataset operations using foundry-platform-sdk.
|
|
91
|
+
|
|
92
|
+
Note: This SDK version requires knowing dataset RIDs in advance.
|
|
93
|
+
Find dataset RIDs in the Foundry web interface.
|
|
94
|
+
|
|
95
|
+
Available commands work with Resource Identifiers (RIDs) like:
|
|
96
|
+
ri.foundry.main.dataset.12345678-1234-1234-1234-123456789abc
|
|
97
|
+
"""
|
|
98
|
+
pass
|
pltr/commands/verify.py
ADDED
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Verification command to test authentication.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import typer
|
|
6
|
+
import requests
|
|
7
|
+
from typing import Optional
|
|
8
|
+
from rich.console import Console
|
|
9
|
+
from rich.table import Table
|
|
10
|
+
|
|
11
|
+
from ..auth.manager import AuthManager
|
|
12
|
+
from ..auth.base import (
|
|
13
|
+
ProfileNotFoundError,
|
|
14
|
+
MissingCredentialsError,
|
|
15
|
+
InvalidCredentialsError,
|
|
16
|
+
)
|
|
17
|
+
|
|
18
|
+
app = typer.Typer()
|
|
19
|
+
console = Console()
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
@app.callback(invoke_without_command=True)
|
|
23
|
+
def verify(
|
|
24
|
+
ctx: typer.Context,
|
|
25
|
+
profile: Optional[str] = typer.Option(
|
|
26
|
+
None, "--profile", "-p", help="Profile to verify"
|
|
27
|
+
),
|
|
28
|
+
):
|
|
29
|
+
"""Verify authentication by connecting to Palantir Foundry."""
|
|
30
|
+
if ctx.invoked_subcommand is not None:
|
|
31
|
+
return
|
|
32
|
+
|
|
33
|
+
auth_manager = AuthManager()
|
|
34
|
+
|
|
35
|
+
# Show which profile we're using
|
|
36
|
+
active_profile = profile or auth_manager.get_current_profile()
|
|
37
|
+
if not active_profile:
|
|
38
|
+
console.print("[red]Error:[/red] No profile configured")
|
|
39
|
+
console.print("Run 'pltr configure configure' to set up authentication")
|
|
40
|
+
raise typer.Exit(1)
|
|
41
|
+
|
|
42
|
+
console.print(f"Verifying profile: [bold]{active_profile}[/bold]")
|
|
43
|
+
|
|
44
|
+
try:
|
|
45
|
+
# Get credentials directly without creating SDK client
|
|
46
|
+
from ..auth.storage import CredentialStorage
|
|
47
|
+
|
|
48
|
+
storage = CredentialStorage()
|
|
49
|
+
|
|
50
|
+
# Get credentials for the profile
|
|
51
|
+
try:
|
|
52
|
+
credentials = storage.get_profile(active_profile)
|
|
53
|
+
except ProfileNotFoundError:
|
|
54
|
+
console.print(f"[red]Error:[/red] Profile '{active_profile}' not found")
|
|
55
|
+
raise typer.Exit(1)
|
|
56
|
+
|
|
57
|
+
# Extract authentication details
|
|
58
|
+
host = credentials.get("host")
|
|
59
|
+
auth_type = credentials.get("auth_type")
|
|
60
|
+
|
|
61
|
+
if not host:
|
|
62
|
+
console.print(
|
|
63
|
+
f"[red]Error:[/red] No host URL configured for profile '{active_profile}'"
|
|
64
|
+
)
|
|
65
|
+
raise typer.Exit(1)
|
|
66
|
+
|
|
67
|
+
# Prepare the request
|
|
68
|
+
url = f"{host}/multipass/api/me"
|
|
69
|
+
headers = {}
|
|
70
|
+
|
|
71
|
+
# Set up authentication headers based on type
|
|
72
|
+
if auth_type == "token":
|
|
73
|
+
token = credentials.get("token")
|
|
74
|
+
if not token:
|
|
75
|
+
console.print(
|
|
76
|
+
f"[red]Error:[/red] No token configured for profile '{active_profile}'"
|
|
77
|
+
)
|
|
78
|
+
raise typer.Exit(1)
|
|
79
|
+
headers["Authorization"] = f"Bearer {token}"
|
|
80
|
+
|
|
81
|
+
elif auth_type == "oauth":
|
|
82
|
+
# OAuth - need to get access token first
|
|
83
|
+
client_id = credentials.get("client_id")
|
|
84
|
+
client_secret = credentials.get("client_secret")
|
|
85
|
+
|
|
86
|
+
if not client_id or not client_secret:
|
|
87
|
+
console.print(
|
|
88
|
+
f"[red]Error:[/red] OAuth credentials incomplete for profile '{active_profile}'"
|
|
89
|
+
)
|
|
90
|
+
raise typer.Exit(1)
|
|
91
|
+
|
|
92
|
+
token_url = f"{host}/multipass/api/oauth2/token"
|
|
93
|
+
token_response = requests.post(
|
|
94
|
+
token_url,
|
|
95
|
+
data={
|
|
96
|
+
"grant_type": "client_credentials",
|
|
97
|
+
"client_id": client_id,
|
|
98
|
+
"client_secret": client_secret,
|
|
99
|
+
},
|
|
100
|
+
)
|
|
101
|
+
if token_response.status_code == 200:
|
|
102
|
+
access_token = token_response.json().get("access_token")
|
|
103
|
+
headers["Authorization"] = f"Bearer {access_token}"
|
|
104
|
+
else:
|
|
105
|
+
console.print(
|
|
106
|
+
f"[red]Error:[/red] Failed to get OAuth token: {token_response.text}"
|
|
107
|
+
)
|
|
108
|
+
raise typer.Exit(1)
|
|
109
|
+
|
|
110
|
+
# Make the request to /multipass/api/me
|
|
111
|
+
response = requests.get(url, headers=headers)
|
|
112
|
+
|
|
113
|
+
if response.status_code == 200:
|
|
114
|
+
user_info = response.json()
|
|
115
|
+
|
|
116
|
+
# Display success with user information
|
|
117
|
+
console.print("[green]✓[/green] Authentication successful!")
|
|
118
|
+
console.print()
|
|
119
|
+
|
|
120
|
+
# Create a table with user information
|
|
121
|
+
table = Table(title="User Information", show_header=False)
|
|
122
|
+
table.add_column("Field", style="cyan")
|
|
123
|
+
table.add_column("Value")
|
|
124
|
+
|
|
125
|
+
# Add user details
|
|
126
|
+
table.add_row("Username", user_info.get("username", "N/A"))
|
|
127
|
+
table.add_row("Email", user_info.get("email", "N/A"))
|
|
128
|
+
table.add_row("User ID", user_info.get("id", "N/A"))
|
|
129
|
+
|
|
130
|
+
# Add organization info if available
|
|
131
|
+
if "organization" in user_info:
|
|
132
|
+
table.add_row(
|
|
133
|
+
"Organization", user_info["organization"].get("rid", "N/A")
|
|
134
|
+
)
|
|
135
|
+
|
|
136
|
+
# Add groups if available
|
|
137
|
+
if "groups" in user_info and user_info["groups"]:
|
|
138
|
+
groups_list = ", ".join(
|
|
139
|
+
g.get("name", "N/A") for g in user_info["groups"][:3]
|
|
140
|
+
)
|
|
141
|
+
if len(user_info["groups"]) > 3:
|
|
142
|
+
groups_list += f" (+{len(user_info['groups']) - 3} more)"
|
|
143
|
+
table.add_row("Groups", groups_list)
|
|
144
|
+
|
|
145
|
+
console.print(table)
|
|
146
|
+
console.print()
|
|
147
|
+
console.print(
|
|
148
|
+
f"[green]Profile '{active_profile}' is properly configured![/green]"
|
|
149
|
+
)
|
|
150
|
+
|
|
151
|
+
elif response.status_code == 401:
|
|
152
|
+
console.print("[red]✗[/red] Authentication failed: Invalid credentials")
|
|
153
|
+
console.print(
|
|
154
|
+
f"Please check your token/credentials for profile '{active_profile}'"
|
|
155
|
+
)
|
|
156
|
+
raise typer.Exit(1)
|
|
157
|
+
elif response.status_code == 403:
|
|
158
|
+
console.print("[red]✗[/red] Authentication failed: Access forbidden")
|
|
159
|
+
console.print(
|
|
160
|
+
"Your credentials are valid but you don't have access to this endpoint"
|
|
161
|
+
)
|
|
162
|
+
raise typer.Exit(1)
|
|
163
|
+
else:
|
|
164
|
+
console.print(f"[red]✗[/red] Request failed: {response.status_code}")
|
|
165
|
+
console.print(f"Response: {response.text}")
|
|
166
|
+
raise typer.Exit(1)
|
|
167
|
+
|
|
168
|
+
except ProfileNotFoundError as e:
|
|
169
|
+
console.print(f"[red]Error:[/red] {e}")
|
|
170
|
+
raise typer.Exit(1)
|
|
171
|
+
except MissingCredentialsError as e:
|
|
172
|
+
console.print(f"[red]Error:[/red] {e}")
|
|
173
|
+
raise typer.Exit(1)
|
|
174
|
+
except InvalidCredentialsError as e:
|
|
175
|
+
console.print(f"[red]Error:[/red] {e}")
|
|
176
|
+
raise typer.Exit(1)
|
|
177
|
+
except requests.exceptions.ConnectionError:
|
|
178
|
+
console.print(
|
|
179
|
+
"[red]✗[/red] Connection failed: Unable to reach the Foundry host"
|
|
180
|
+
)
|
|
181
|
+
console.print("Please check your network connection and host URL")
|
|
182
|
+
raise typer.Exit(1)
|
|
183
|
+
except Exception as e:
|
|
184
|
+
console.print(f"[red]Error:[/red] Unexpected error: {e}")
|
|
185
|
+
raise typer.Exit(1)
|
pltr/config/__init__.py
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Configuration management for pltr."""
|
pltr/config/profiles.py
ADDED
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Profile management for pltr CLI.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import json
|
|
6
|
+
from typing import List, Optional
|
|
7
|
+
|
|
8
|
+
from .settings import Settings
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class ProfileManager:
|
|
12
|
+
"""Manages authentication profiles."""
|
|
13
|
+
|
|
14
|
+
def __init__(self):
|
|
15
|
+
"""Initialize profile manager."""
|
|
16
|
+
self.settings = Settings()
|
|
17
|
+
self.profiles_file = self.settings.config_dir / "profiles.json"
|
|
18
|
+
self._profiles = self._load_profiles()
|
|
19
|
+
|
|
20
|
+
def _load_profiles(self) -> dict:
|
|
21
|
+
"""Load profiles from file."""
|
|
22
|
+
if not self.profiles_file.exists():
|
|
23
|
+
return {"profiles": [], "default": None}
|
|
24
|
+
|
|
25
|
+
try:
|
|
26
|
+
with open(self.profiles_file, "r") as f:
|
|
27
|
+
return json.load(f)
|
|
28
|
+
except (json.JSONDecodeError, IOError):
|
|
29
|
+
return {"profiles": [], "default": None}
|
|
30
|
+
|
|
31
|
+
def _save_profiles(self) -> None:
|
|
32
|
+
"""Save profiles to file."""
|
|
33
|
+
with open(self.profiles_file, "w") as f:
|
|
34
|
+
json.dump(self._profiles, f, indent=2)
|
|
35
|
+
|
|
36
|
+
def add_profile(self, profile: str) -> None:
|
|
37
|
+
"""
|
|
38
|
+
Add a new profile.
|
|
39
|
+
|
|
40
|
+
Args:
|
|
41
|
+
profile: Profile name
|
|
42
|
+
"""
|
|
43
|
+
if profile not in self._profiles["profiles"]:
|
|
44
|
+
self._profiles["profiles"].append(profile)
|
|
45
|
+
self._save_profiles()
|
|
46
|
+
|
|
47
|
+
def remove_profile(self, profile: str) -> None:
|
|
48
|
+
"""
|
|
49
|
+
Remove a profile.
|
|
50
|
+
|
|
51
|
+
Args:
|
|
52
|
+
profile: Profile name
|
|
53
|
+
"""
|
|
54
|
+
if profile in self._profiles["profiles"]:
|
|
55
|
+
self._profiles["profiles"].remove(profile)
|
|
56
|
+
|
|
57
|
+
# If this was the default profile, clear the default
|
|
58
|
+
if self._profiles["default"] == profile:
|
|
59
|
+
self._profiles["default"] = None
|
|
60
|
+
# Set a new default if there are other profiles
|
|
61
|
+
if self._profiles["profiles"]:
|
|
62
|
+
self._profiles["default"] = self._profiles["profiles"][0]
|
|
63
|
+
|
|
64
|
+
self._save_profiles()
|
|
65
|
+
|
|
66
|
+
def list_profiles(self) -> List[str]:
|
|
67
|
+
"""
|
|
68
|
+
List all profiles.
|
|
69
|
+
|
|
70
|
+
Returns:
|
|
71
|
+
List of profile names
|
|
72
|
+
"""
|
|
73
|
+
return self._profiles["profiles"].copy()
|
|
74
|
+
|
|
75
|
+
def get_default(self) -> Optional[str]:
|
|
76
|
+
"""
|
|
77
|
+
Get the default profile.
|
|
78
|
+
|
|
79
|
+
Returns:
|
|
80
|
+
Default profile name or None
|
|
81
|
+
"""
|
|
82
|
+
return self._profiles["default"]
|
|
83
|
+
|
|
84
|
+
def set_default(self, profile: str) -> None:
|
|
85
|
+
"""
|
|
86
|
+
Set the default profile.
|
|
87
|
+
|
|
88
|
+
Args:
|
|
89
|
+
profile: Profile name
|
|
90
|
+
"""
|
|
91
|
+
if profile in self._profiles["profiles"]:
|
|
92
|
+
self._profiles["default"] = profile
|
|
93
|
+
self._save_profiles()
|
|
94
|
+
# Also update in settings
|
|
95
|
+
self.settings.set("default_profile", profile)
|
|
96
|
+
|
|
97
|
+
def profile_exists(self, profile: str) -> bool:
|
|
98
|
+
"""
|
|
99
|
+
Check if a profile exists.
|
|
100
|
+
|
|
101
|
+
Args:
|
|
102
|
+
profile: Profile name
|
|
103
|
+
|
|
104
|
+
Returns:
|
|
105
|
+
True if profile exists, False otherwise
|
|
106
|
+
"""
|
|
107
|
+
return profile in self._profiles["profiles"]
|
|
108
|
+
|
|
109
|
+
def get_active_profile(self) -> Optional[str]:
|
|
110
|
+
"""
|
|
111
|
+
Get the active profile (from environment or default).
|
|
112
|
+
|
|
113
|
+
Returns:
|
|
114
|
+
Active profile name
|
|
115
|
+
"""
|
|
116
|
+
import os
|
|
117
|
+
|
|
118
|
+
# Check environment variable first
|
|
119
|
+
env_profile = os.environ.get("PLTR_PROFILE")
|
|
120
|
+
if env_profile and self.profile_exists(env_profile):
|
|
121
|
+
return env_profile
|
|
122
|
+
|
|
123
|
+
# Fall back to default
|
|
124
|
+
return self.get_default()
|
pltr/config/settings.py
ADDED
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Configuration settings management for pltr CLI.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import os
|
|
6
|
+
import json
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
from typing import Dict, Any
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class Settings:
|
|
12
|
+
"""Manages application settings and configuration."""
|
|
13
|
+
|
|
14
|
+
def __init__(self):
|
|
15
|
+
"""Initialize settings manager."""
|
|
16
|
+
self.config_dir = self._get_config_dir()
|
|
17
|
+
self.settings_file = self.config_dir / "settings.json"
|
|
18
|
+
self._ensure_config_dir()
|
|
19
|
+
self._settings = self._load_settings()
|
|
20
|
+
|
|
21
|
+
def _get_config_dir(self) -> Path:
|
|
22
|
+
"""Get the configuration directory path."""
|
|
23
|
+
# Follow XDG Base Directory specification
|
|
24
|
+
xdg_config_home = os.environ.get("XDG_CONFIG_HOME")
|
|
25
|
+
if xdg_config_home:
|
|
26
|
+
return Path(xdg_config_home) / "pltr"
|
|
27
|
+
|
|
28
|
+
# Default to ~/.config/pltr
|
|
29
|
+
return Path.home() / ".config" / "pltr"
|
|
30
|
+
|
|
31
|
+
def _ensure_config_dir(self) -> None:
|
|
32
|
+
"""Ensure configuration directory exists."""
|
|
33
|
+
self.config_dir.mkdir(parents=True, exist_ok=True)
|
|
34
|
+
|
|
35
|
+
def _load_settings(self) -> Dict[str, Any]:
|
|
36
|
+
"""Load settings from file."""
|
|
37
|
+
if not self.settings_file.exists():
|
|
38
|
+
return self._get_default_settings()
|
|
39
|
+
|
|
40
|
+
try:
|
|
41
|
+
with open(self.settings_file, "r") as f:
|
|
42
|
+
return json.load(f)
|
|
43
|
+
except (json.JSONDecodeError, IOError):
|
|
44
|
+
return self._get_default_settings()
|
|
45
|
+
|
|
46
|
+
def _get_default_settings(self) -> Dict[str, Any]:
|
|
47
|
+
"""Get default settings."""
|
|
48
|
+
return {
|
|
49
|
+
"default_profile": "default",
|
|
50
|
+
"output_format": "table",
|
|
51
|
+
"color_output": True,
|
|
52
|
+
"page_size": 20,
|
|
53
|
+
"timeout": 30,
|
|
54
|
+
"verify_ssl": True,
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
def save(self) -> None:
|
|
58
|
+
"""Save settings to file."""
|
|
59
|
+
with open(self.settings_file, "w") as f:
|
|
60
|
+
json.dump(self._settings, f, indent=2)
|
|
61
|
+
|
|
62
|
+
def get(self, key: str, default: Any = None) -> Any:
|
|
63
|
+
"""
|
|
64
|
+
Get a setting value.
|
|
65
|
+
|
|
66
|
+
Args:
|
|
67
|
+
key: Setting key
|
|
68
|
+
default: Default value if key doesn't exist
|
|
69
|
+
|
|
70
|
+
Returns:
|
|
71
|
+
Setting value or default
|
|
72
|
+
"""
|
|
73
|
+
return self._settings.get(key, default)
|
|
74
|
+
|
|
75
|
+
def set(self, key: str, value: Any) -> None:
|
|
76
|
+
"""
|
|
77
|
+
Set a setting value.
|
|
78
|
+
|
|
79
|
+
Args:
|
|
80
|
+
key: Setting key
|
|
81
|
+
value: Setting value
|
|
82
|
+
"""
|
|
83
|
+
self._settings[key] = value
|
|
84
|
+
self.save()
|
|
85
|
+
|
|
86
|
+
def update(self, settings: Dict[str, Any]) -> None:
|
|
87
|
+
"""
|
|
88
|
+
Update multiple settings.
|
|
89
|
+
|
|
90
|
+
Args:
|
|
91
|
+
settings: Dictionary of settings to update
|
|
92
|
+
"""
|
|
93
|
+
self._settings.update(settings)
|
|
94
|
+
self.save()
|
|
95
|
+
|
|
96
|
+
def reset(self) -> None:
|
|
97
|
+
"""Reset settings to defaults."""
|
|
98
|
+
self._settings = self._get_default_settings()
|
|
99
|
+
self.save()
|
|
100
|
+
|
|
101
|
+
def get_all(self) -> Dict[str, Any]:
|
|
102
|
+
"""Get all settings."""
|
|
103
|
+
return self._settings.copy()
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Service wrappers for Foundry SDK."""
|
pltr/services/base.py
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Base service class for Foundry API wrappers.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from typing import Any, Optional
|
|
6
|
+
from abc import ABC, abstractmethod
|
|
7
|
+
|
|
8
|
+
from ..auth.manager import AuthManager
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class BaseService(ABC):
|
|
12
|
+
"""Base class for Foundry service wrappers."""
|
|
13
|
+
|
|
14
|
+
def __init__(self, profile: Optional[str] = None):
|
|
15
|
+
"""
|
|
16
|
+
Initialize base service.
|
|
17
|
+
|
|
18
|
+
Args:
|
|
19
|
+
profile: Authentication profile name (uses default if not specified)
|
|
20
|
+
"""
|
|
21
|
+
self.profile = profile
|
|
22
|
+
self.auth_manager = AuthManager()
|
|
23
|
+
self._client: Optional[Any] = None
|
|
24
|
+
|
|
25
|
+
@property
|
|
26
|
+
def client(self) -> Any:
|
|
27
|
+
"""
|
|
28
|
+
Get authenticated Foundry client.
|
|
29
|
+
|
|
30
|
+
Returns:
|
|
31
|
+
Configured FoundryClient instance
|
|
32
|
+
|
|
33
|
+
Raises:
|
|
34
|
+
ProfileNotFoundError: If profile doesn't exist
|
|
35
|
+
MissingCredentialsError: If credentials are incomplete
|
|
36
|
+
"""
|
|
37
|
+
if self._client is None:
|
|
38
|
+
self._client = self.auth_manager.get_client(self.profile)
|
|
39
|
+
return self._client
|
|
40
|
+
|
|
41
|
+
@abstractmethod
|
|
42
|
+
def _get_service(self) -> Any:
|
|
43
|
+
"""
|
|
44
|
+
Get the specific Foundry SDK service instance.
|
|
45
|
+
|
|
46
|
+
Returns:
|
|
47
|
+
Configured service instance from foundry-platform-sdk
|
|
48
|
+
|
|
49
|
+
This method should be implemented by subclasses to return the
|
|
50
|
+
appropriate service (e.g., client.datasets, client.ontology)
|
|
51
|
+
"""
|
|
52
|
+
pass
|
|
53
|
+
|
|
54
|
+
@property
|
|
55
|
+
def service(self) -> Any:
|
|
56
|
+
"""
|
|
57
|
+
Get the Foundry SDK service instance.
|
|
58
|
+
|
|
59
|
+
Returns:
|
|
60
|
+
Configured service instance
|
|
61
|
+
"""
|
|
62
|
+
return self._get_service()
|
pltr/services/dataset.py
ADDED
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Dataset service wrapper for Foundry SDK v2 API.
|
|
3
|
+
Only includes operations that are actually supported by foundry-platform-sdk v1.27.0.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from typing import Any, Optional, Dict
|
|
7
|
+
|
|
8
|
+
from .base import BaseService
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class DatasetService(BaseService):
|
|
12
|
+
"""Service wrapper for Foundry dataset operations using v2 API."""
|
|
13
|
+
|
|
14
|
+
def _get_service(self) -> Any:
|
|
15
|
+
"""Get the Foundry datasets service."""
|
|
16
|
+
return self.client.datasets
|
|
17
|
+
|
|
18
|
+
def get_dataset(self, dataset_rid: str) -> Dict[str, Any]:
|
|
19
|
+
"""
|
|
20
|
+
Get information about a specific dataset.
|
|
21
|
+
|
|
22
|
+
Args:
|
|
23
|
+
dataset_rid: Dataset Resource Identifier
|
|
24
|
+
|
|
25
|
+
Returns:
|
|
26
|
+
Dataset information dictionary
|
|
27
|
+
"""
|
|
28
|
+
try:
|
|
29
|
+
# Use the v2 API's Dataset.get method
|
|
30
|
+
dataset = self.service.Dataset.get(dataset_rid)
|
|
31
|
+
return self._format_dataset_info(dataset)
|
|
32
|
+
except Exception as e:
|
|
33
|
+
raise RuntimeError(f"Failed to get dataset {dataset_rid}: {e}")
|
|
34
|
+
|
|
35
|
+
# get_schema method removed - uses preview-only API
|
|
36
|
+
|
|
37
|
+
def create_dataset(
|
|
38
|
+
self, name: str, parent_folder_rid: Optional[str] = None
|
|
39
|
+
) -> Dict[str, Any]:
|
|
40
|
+
"""
|
|
41
|
+
Create a new dataset.
|
|
42
|
+
|
|
43
|
+
Args:
|
|
44
|
+
name: Dataset name
|
|
45
|
+
parent_folder_rid: Parent folder RID (optional)
|
|
46
|
+
|
|
47
|
+
Returns:
|
|
48
|
+
Created dataset information
|
|
49
|
+
"""
|
|
50
|
+
try:
|
|
51
|
+
# The create method parameters depend on the SDK version
|
|
52
|
+
dataset = self.service.Dataset.create(
|
|
53
|
+
name=name, parent_folder_rid=parent_folder_rid
|
|
54
|
+
)
|
|
55
|
+
return self._format_dataset_info(dataset)
|
|
56
|
+
except Exception as e:
|
|
57
|
+
raise RuntimeError(f"Failed to create dataset '{name}': {e}")
|
|
58
|
+
|
|
59
|
+
def read_table(self, dataset_rid: str, format: str = "arrow") -> Any:
|
|
60
|
+
"""
|
|
61
|
+
Read dataset as a table.
|
|
62
|
+
|
|
63
|
+
Args:
|
|
64
|
+
dataset_rid: Dataset Resource Identifier
|
|
65
|
+
format: Output format (arrow, pandas, etc.)
|
|
66
|
+
|
|
67
|
+
Returns:
|
|
68
|
+
Table data in specified format
|
|
69
|
+
"""
|
|
70
|
+
try:
|
|
71
|
+
return self.service.Dataset.read_table(dataset_rid, format=format)
|
|
72
|
+
except Exception as e:
|
|
73
|
+
raise RuntimeError(f"Failed to read dataset {dataset_rid}: {e}")
|
|
74
|
+
|
|
75
|
+
def _format_dataset_info(self, dataset: Any) -> Dict[str, Any]:
|
|
76
|
+
"""
|
|
77
|
+
Format dataset information for consistent output.
|
|
78
|
+
|
|
79
|
+
Args:
|
|
80
|
+
dataset: Dataset object from Foundry SDK
|
|
81
|
+
|
|
82
|
+
Returns:
|
|
83
|
+
Formatted dataset information dictionary
|
|
84
|
+
"""
|
|
85
|
+
# The v2 Dataset object only has rid, name, and parent_folder_rid
|
|
86
|
+
return {
|
|
87
|
+
"rid": dataset.rid,
|
|
88
|
+
"name": dataset.name,
|
|
89
|
+
"parent_folder_rid": dataset.parent_folder_rid,
|
|
90
|
+
}
|