pltr-cli 0.2.0__py3-none-any.whl → 0.4.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/__main__.py +3 -0
- pltr/cli.py +4 -0
- pltr/commands/alias.py +241 -0
- pltr/commands/folder.py +338 -0
- pltr/commands/ontology.py +2 -8
- pltr/config/aliases.py +254 -0
- pltr/services/folder.py +167 -0
- pltr/services/ontology.py +17 -18
- pltr/utils/alias_resolver.py +56 -0
- pltr/utils/completion.py +8 -0
- {pltr_cli-0.2.0.dist-info → pltr_cli-0.4.0.dist-info}/METADATA +10 -3
- {pltr_cli-0.2.0.dist-info → pltr_cli-0.4.0.dist-info}/RECORD +15 -10
- {pltr_cli-0.2.0.dist-info → pltr_cli-0.4.0.dist-info}/WHEEL +0 -0
- {pltr_cli-0.2.0.dist-info → pltr_cli-0.4.0.dist-info}/entry_points.txt +0 -0
- {pltr_cli-0.2.0.dist-info → pltr_cli-0.4.0.dist-info}/licenses/LICENSE +0 -0
pltr/__main__.py
CHANGED
|
@@ -23,6 +23,9 @@ if "_PLTR_COMPLETE" in os.environ:
|
|
|
23
23
|
|
|
24
24
|
# Normal CLI execution
|
|
25
25
|
from pltr.cli import app
|
|
26
|
+
from pltr.utils.alias_resolver import inject_alias_resolution
|
|
26
27
|
|
|
27
28
|
if __name__ == "__main__":
|
|
29
|
+
# Resolve aliases before running the app
|
|
30
|
+
inject_alias_resolution()
|
|
28
31
|
app()
|
pltr/cli.py
CHANGED
|
@@ -10,11 +10,13 @@ from pltr.commands import (
|
|
|
10
10
|
configure,
|
|
11
11
|
verify,
|
|
12
12
|
dataset,
|
|
13
|
+
folder,
|
|
13
14
|
ontology,
|
|
14
15
|
sql,
|
|
15
16
|
admin,
|
|
16
17
|
shell,
|
|
17
18
|
completion,
|
|
19
|
+
alias,
|
|
18
20
|
)
|
|
19
21
|
|
|
20
22
|
app = typer.Typer(
|
|
@@ -27,6 +29,7 @@ app = typer.Typer(
|
|
|
27
29
|
app.add_typer(configure.app, name="configure", help="Manage authentication profiles")
|
|
28
30
|
app.add_typer(verify.app, name="verify", help="Verify authentication")
|
|
29
31
|
app.add_typer(dataset.app, name="dataset", help="Manage datasets")
|
|
32
|
+
app.add_typer(folder.app, name="folder", help="Manage folders")
|
|
30
33
|
app.add_typer(ontology.app, name="ontology", help="Ontology operations")
|
|
31
34
|
app.add_typer(sql.app, name="sql", help="Execute SQL queries")
|
|
32
35
|
app.add_typer(
|
|
@@ -36,6 +39,7 @@ app.add_typer(
|
|
|
36
39
|
)
|
|
37
40
|
app.add_typer(shell.shell_app, name="shell", help="Interactive shell mode")
|
|
38
41
|
app.add_typer(completion.app, name="completion", help="Manage shell completions")
|
|
42
|
+
app.add_typer(alias.app, name="alias", help="Manage command aliases")
|
|
39
43
|
|
|
40
44
|
|
|
41
45
|
def version_callback(value: bool):
|
pltr/commands/alias.py
ADDED
|
@@ -0,0 +1,241 @@
|
|
|
1
|
+
"""Command alias management commands."""
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
from typing import Optional
|
|
5
|
+
|
|
6
|
+
import typer
|
|
7
|
+
from rich import print as rprint
|
|
8
|
+
|
|
9
|
+
from pltr.config.aliases import AliasManager
|
|
10
|
+
from pltr.utils.completion import complete_alias_names
|
|
11
|
+
|
|
12
|
+
app = typer.Typer(name="alias", help="Manage command aliases", no_args_is_help=True)
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
@app.command()
|
|
16
|
+
def add(
|
|
17
|
+
name: str = typer.Argument(..., help="Alias name"),
|
|
18
|
+
command: str = typer.Argument(..., help="Command to alias"),
|
|
19
|
+
force: bool = typer.Option(False, "--force", "-f", help="Overwrite existing alias"),
|
|
20
|
+
) -> None:
|
|
21
|
+
"""Create a new command alias."""
|
|
22
|
+
manager = AliasManager()
|
|
23
|
+
|
|
24
|
+
# Check if alias already exists
|
|
25
|
+
if manager.get_alias(name) and not force:
|
|
26
|
+
rprint(f"[red]Alias '{name}' already exists. Use --force to overwrite.[/red]")
|
|
27
|
+
raise typer.Exit(1)
|
|
28
|
+
|
|
29
|
+
# Check for reserved command names
|
|
30
|
+
reserved_commands = [
|
|
31
|
+
"configure",
|
|
32
|
+
"verify",
|
|
33
|
+
"dataset",
|
|
34
|
+
"ontology",
|
|
35
|
+
"sql",
|
|
36
|
+
"admin",
|
|
37
|
+
"shell",
|
|
38
|
+
"completion",
|
|
39
|
+
"alias",
|
|
40
|
+
"help",
|
|
41
|
+
"--version",
|
|
42
|
+
]
|
|
43
|
+
if name in reserved_commands:
|
|
44
|
+
rprint(f"[red]Cannot use reserved command name '{name}' as alias[/red]")
|
|
45
|
+
raise typer.Exit(1)
|
|
46
|
+
|
|
47
|
+
try:
|
|
48
|
+
if force and manager.get_alias(name):
|
|
49
|
+
success = manager.edit_alias(name, command)
|
|
50
|
+
action = "Updated"
|
|
51
|
+
else:
|
|
52
|
+
success = manager.add_alias(name, command)
|
|
53
|
+
action = "Created"
|
|
54
|
+
|
|
55
|
+
if success:
|
|
56
|
+
rprint(f"[green]{action} alias:[/green] {name} → {command}")
|
|
57
|
+
else:
|
|
58
|
+
rprint(f"[red]Failed to create alias '{name}'[/red]")
|
|
59
|
+
raise typer.Exit(1)
|
|
60
|
+
except ValueError as e:
|
|
61
|
+
rprint(f"[red]Error: {e}[/red]")
|
|
62
|
+
raise typer.Exit(1)
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
@app.command()
|
|
66
|
+
def remove(
|
|
67
|
+
name: str = typer.Argument(
|
|
68
|
+
..., help="Alias name to remove", autocompletion=complete_alias_names
|
|
69
|
+
),
|
|
70
|
+
confirm: bool = typer.Option(
|
|
71
|
+
True, "--confirm/--no-confirm", help="Confirm removal"
|
|
72
|
+
),
|
|
73
|
+
) -> None:
|
|
74
|
+
"""Remove a command alias."""
|
|
75
|
+
manager = AliasManager()
|
|
76
|
+
|
|
77
|
+
command = manager.get_alias(name)
|
|
78
|
+
if not command:
|
|
79
|
+
rprint(f"[red]Alias '{name}' not found[/red]")
|
|
80
|
+
raise typer.Exit(1)
|
|
81
|
+
|
|
82
|
+
if confirm:
|
|
83
|
+
confirmation = typer.confirm(f"Remove alias '{name}' → {command}?")
|
|
84
|
+
if not confirmation:
|
|
85
|
+
rprint("[yellow]Removal cancelled[/yellow]")
|
|
86
|
+
return
|
|
87
|
+
|
|
88
|
+
if manager.remove_alias(name):
|
|
89
|
+
rprint(f"[green]Removed alias '{name}'[/green]")
|
|
90
|
+
else:
|
|
91
|
+
rprint(f"[red]Failed to remove alias '{name}'[/red]")
|
|
92
|
+
raise typer.Exit(1)
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
@app.command()
|
|
96
|
+
def edit(
|
|
97
|
+
name: str = typer.Argument(
|
|
98
|
+
..., help="Alias name to edit", autocompletion=complete_alias_names
|
|
99
|
+
),
|
|
100
|
+
command: str = typer.Argument(..., help="New command for the alias"),
|
|
101
|
+
) -> None:
|
|
102
|
+
"""Edit an existing command alias."""
|
|
103
|
+
manager = AliasManager()
|
|
104
|
+
|
|
105
|
+
if not manager.get_alias(name):
|
|
106
|
+
rprint(f"[red]Alias '{name}' not found[/red]")
|
|
107
|
+
raise typer.Exit(1)
|
|
108
|
+
|
|
109
|
+
try:
|
|
110
|
+
if manager.edit_alias(name, command):
|
|
111
|
+
rprint(f"[green]Updated alias:[/green] {name} → {command}")
|
|
112
|
+
else:
|
|
113
|
+
rprint(f"[red]Failed to edit alias '{name}'[/red]")
|
|
114
|
+
raise typer.Exit(1)
|
|
115
|
+
except ValueError as e:
|
|
116
|
+
rprint(f"[red]Error: {e}[/red]")
|
|
117
|
+
raise typer.Exit(1)
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
@app.command("list")
|
|
121
|
+
def list_aliases() -> None:
|
|
122
|
+
"""List all command aliases."""
|
|
123
|
+
manager = AliasManager()
|
|
124
|
+
manager.display_aliases()
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
@app.command()
|
|
128
|
+
def show(
|
|
129
|
+
name: str = typer.Argument(
|
|
130
|
+
..., help="Alias name to show", autocompletion=complete_alias_names
|
|
131
|
+
),
|
|
132
|
+
) -> None:
|
|
133
|
+
"""Show details of a specific alias."""
|
|
134
|
+
manager = AliasManager()
|
|
135
|
+
manager.display_aliases(name)
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
@app.command()
|
|
139
|
+
def clear(
|
|
140
|
+
confirm: bool = typer.Option(
|
|
141
|
+
True, "--confirm/--no-confirm", help="Confirm clearing all aliases"
|
|
142
|
+
),
|
|
143
|
+
) -> None:
|
|
144
|
+
"""Clear all command aliases."""
|
|
145
|
+
manager = AliasManager()
|
|
146
|
+
|
|
147
|
+
aliases = manager.list_aliases()
|
|
148
|
+
if not aliases:
|
|
149
|
+
rprint("[yellow]No aliases to clear[/yellow]")
|
|
150
|
+
return
|
|
151
|
+
|
|
152
|
+
if confirm:
|
|
153
|
+
confirmation = typer.confirm(f"Clear all {len(aliases)} aliases?")
|
|
154
|
+
if not confirmation:
|
|
155
|
+
rprint("[yellow]Clear cancelled[/yellow]")
|
|
156
|
+
return
|
|
157
|
+
|
|
158
|
+
count = manager.clear_all()
|
|
159
|
+
rprint(f"[green]Cleared {count} aliases[/green]")
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
@app.command("export")
|
|
163
|
+
def export_aliases(
|
|
164
|
+
output: Optional[str] = typer.Option(
|
|
165
|
+
None, "--output", "-o", help="Output file (default: stdout)"
|
|
166
|
+
),
|
|
167
|
+
) -> None:
|
|
168
|
+
"""Export aliases to JSON format."""
|
|
169
|
+
manager = AliasManager()
|
|
170
|
+
aliases = manager.export_aliases()
|
|
171
|
+
|
|
172
|
+
if not aliases:
|
|
173
|
+
rprint("[yellow]No aliases to export[/yellow]")
|
|
174
|
+
return
|
|
175
|
+
|
|
176
|
+
json_data = json.dumps(aliases, indent=2, sort_keys=True)
|
|
177
|
+
|
|
178
|
+
if output:
|
|
179
|
+
with open(output, "w") as f:
|
|
180
|
+
f.write(json_data)
|
|
181
|
+
rprint(f"[green]Exported {len(aliases)} aliases to {output}[/green]")
|
|
182
|
+
else:
|
|
183
|
+
print(json_data)
|
|
184
|
+
|
|
185
|
+
|
|
186
|
+
@app.command("import")
|
|
187
|
+
def import_aliases(
|
|
188
|
+
input_file: str = typer.Argument(..., help="JSON file containing aliases"),
|
|
189
|
+
merge: bool = typer.Option(
|
|
190
|
+
False, "--merge", "-m", help="Merge with existing aliases (default: replace)"
|
|
191
|
+
),
|
|
192
|
+
) -> None:
|
|
193
|
+
"""Import aliases from a JSON file."""
|
|
194
|
+
manager = AliasManager()
|
|
195
|
+
|
|
196
|
+
try:
|
|
197
|
+
with open(input_file, "r") as f:
|
|
198
|
+
data = json.load(f)
|
|
199
|
+
except (json.JSONDecodeError, IOError) as e:
|
|
200
|
+
rprint(f"[red]Error reading file: {e}[/red]")
|
|
201
|
+
raise typer.Exit(1)
|
|
202
|
+
|
|
203
|
+
if not isinstance(data, dict):
|
|
204
|
+
rprint("[red]Invalid format: expected JSON object with alias mappings[/red]")
|
|
205
|
+
raise typer.Exit(1)
|
|
206
|
+
|
|
207
|
+
# Clear existing aliases if not merging
|
|
208
|
+
if not merge and manager.list_aliases():
|
|
209
|
+
confirmation = typer.confirm("Replace existing aliases?")
|
|
210
|
+
if not confirmation:
|
|
211
|
+
rprint("[yellow]Import cancelled[/yellow]")
|
|
212
|
+
return
|
|
213
|
+
manager.clear_all()
|
|
214
|
+
|
|
215
|
+
count = manager.import_aliases(data)
|
|
216
|
+
rprint(f"[green]Imported {count} aliases from {input_file}[/green]")
|
|
217
|
+
|
|
218
|
+
|
|
219
|
+
@app.command()
|
|
220
|
+
def resolve(
|
|
221
|
+
command: str = typer.Argument(
|
|
222
|
+
..., help="Command to resolve", autocompletion=complete_alias_names
|
|
223
|
+
),
|
|
224
|
+
) -> None:
|
|
225
|
+
"""Resolve an alias to show the actual command."""
|
|
226
|
+
manager = AliasManager()
|
|
227
|
+
|
|
228
|
+
resolved = manager.resolve_alias(command)
|
|
229
|
+
if resolved == command:
|
|
230
|
+
if command in manager.list_aliases():
|
|
231
|
+
rprint(
|
|
232
|
+
f"[yellow]'{command}' is an alias but may have circular references[/yellow]"
|
|
233
|
+
)
|
|
234
|
+
else:
|
|
235
|
+
rprint(f"[yellow]'{command}' is not an alias[/yellow]")
|
|
236
|
+
else:
|
|
237
|
+
rprint(f"[green]{command}[/green] → {resolved}")
|
|
238
|
+
|
|
239
|
+
|
|
240
|
+
if __name__ == "__main__":
|
|
241
|
+
app()
|
pltr/commands/folder.py
ADDED
|
@@ -0,0 +1,338 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Folder management commands for Foundry filesystem.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import typer
|
|
6
|
+
from typing import Optional, List
|
|
7
|
+
from rich.console import Console
|
|
8
|
+
from rich.table import Table
|
|
9
|
+
|
|
10
|
+
from ..services.folder import FolderService
|
|
11
|
+
from ..utils.formatting import OutputFormatter
|
|
12
|
+
from ..utils.progress import SpinnerProgressTracker
|
|
13
|
+
from ..auth.base import ProfileNotFoundError, MissingCredentialsError
|
|
14
|
+
from ..utils.completion import (
|
|
15
|
+
complete_rid,
|
|
16
|
+
complete_profile,
|
|
17
|
+
complete_output_format,
|
|
18
|
+
cache_rid,
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
app = typer.Typer()
|
|
22
|
+
console = Console()
|
|
23
|
+
formatter = OutputFormatter(console)
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
@app.command("create")
|
|
27
|
+
def create_folder(
|
|
28
|
+
name: str = typer.Argument(..., help="Folder display name"),
|
|
29
|
+
parent_folder: str = typer.Option(
|
|
30
|
+
"ri.compass.main.folder.0",
|
|
31
|
+
"--parent-folder",
|
|
32
|
+
"-p",
|
|
33
|
+
help="Parent folder RID (default: root folder)",
|
|
34
|
+
autocompletion=complete_rid,
|
|
35
|
+
),
|
|
36
|
+
profile: Optional[str] = typer.Option(
|
|
37
|
+
None, "--profile", help="Profile name", autocompletion=complete_profile
|
|
38
|
+
),
|
|
39
|
+
format: str = typer.Option(
|
|
40
|
+
"table",
|
|
41
|
+
"--format",
|
|
42
|
+
"-f",
|
|
43
|
+
help="Output format (table, json, csv)",
|
|
44
|
+
autocompletion=complete_output_format,
|
|
45
|
+
),
|
|
46
|
+
):
|
|
47
|
+
"""Create a new folder in Foundry."""
|
|
48
|
+
try:
|
|
49
|
+
service = FolderService(profile=profile)
|
|
50
|
+
|
|
51
|
+
with SpinnerProgressTracker().track_spinner(f"Creating folder '{name}'..."):
|
|
52
|
+
folder = service.create_folder(
|
|
53
|
+
display_name=name, parent_folder_rid=parent_folder
|
|
54
|
+
)
|
|
55
|
+
|
|
56
|
+
# Cache the RID for future completions
|
|
57
|
+
if folder.get("rid"):
|
|
58
|
+
cache_rid(folder["rid"])
|
|
59
|
+
|
|
60
|
+
formatter.print_success(f"Successfully created folder '{name}'")
|
|
61
|
+
formatter.print_info(f"Folder RID: {folder.get('rid', 'unknown')}")
|
|
62
|
+
|
|
63
|
+
# Format output
|
|
64
|
+
if format == "json":
|
|
65
|
+
formatter.format_dict(folder)
|
|
66
|
+
elif format == "csv":
|
|
67
|
+
formatter.format_list([folder])
|
|
68
|
+
else:
|
|
69
|
+
_format_folder_table(folder)
|
|
70
|
+
|
|
71
|
+
except (ProfileNotFoundError, MissingCredentialsError) as e:
|
|
72
|
+
formatter.print_error(f"Authentication error: {e}")
|
|
73
|
+
raise typer.Exit(1)
|
|
74
|
+
except Exception as e:
|
|
75
|
+
formatter.print_error(f"Failed to create folder: {e}")
|
|
76
|
+
raise typer.Exit(1)
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
@app.command("get")
|
|
80
|
+
def get_folder(
|
|
81
|
+
folder_rid: str = typer.Argument(
|
|
82
|
+
..., help="Folder Resource Identifier", autocompletion=complete_rid
|
|
83
|
+
),
|
|
84
|
+
profile: Optional[str] = typer.Option(
|
|
85
|
+
None, "--profile", help="Profile name", autocompletion=complete_profile
|
|
86
|
+
),
|
|
87
|
+
format: str = typer.Option(
|
|
88
|
+
"table",
|
|
89
|
+
"--format",
|
|
90
|
+
"-f",
|
|
91
|
+
help="Output format (table, json, csv)",
|
|
92
|
+
autocompletion=complete_output_format,
|
|
93
|
+
),
|
|
94
|
+
output: Optional[str] = typer.Option(
|
|
95
|
+
None, "--output", "-o", help="Output file path"
|
|
96
|
+
),
|
|
97
|
+
):
|
|
98
|
+
"""Get detailed information about a specific folder."""
|
|
99
|
+
try:
|
|
100
|
+
# Cache the RID for future completions
|
|
101
|
+
cache_rid(folder_rid)
|
|
102
|
+
|
|
103
|
+
service = FolderService(profile=profile)
|
|
104
|
+
|
|
105
|
+
with SpinnerProgressTracker().track_spinner(f"Fetching folder {folder_rid}..."):
|
|
106
|
+
folder = service.get_folder(folder_rid)
|
|
107
|
+
|
|
108
|
+
# Format output
|
|
109
|
+
if format == "json":
|
|
110
|
+
if output:
|
|
111
|
+
formatter.save_to_file(folder, output, "json")
|
|
112
|
+
else:
|
|
113
|
+
formatter.format_dict(folder)
|
|
114
|
+
elif format == "csv":
|
|
115
|
+
if output:
|
|
116
|
+
formatter.save_to_file([folder], output, "csv")
|
|
117
|
+
else:
|
|
118
|
+
formatter.format_list([folder])
|
|
119
|
+
else:
|
|
120
|
+
_format_folder_table(folder)
|
|
121
|
+
|
|
122
|
+
if output:
|
|
123
|
+
formatter.print_success(f"Folder information saved to {output}")
|
|
124
|
+
|
|
125
|
+
except (ProfileNotFoundError, MissingCredentialsError) as e:
|
|
126
|
+
formatter.print_error(f"Authentication error: {e}")
|
|
127
|
+
raise typer.Exit(1)
|
|
128
|
+
except Exception as e:
|
|
129
|
+
formatter.print_error(f"Failed to get folder: {e}")
|
|
130
|
+
raise typer.Exit(1)
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
@app.command("list")
|
|
134
|
+
def list_children(
|
|
135
|
+
folder_rid: str = typer.Argument(
|
|
136
|
+
...,
|
|
137
|
+
help="Folder Resource Identifier (use 'ri.compass.main.folder.0' for root)",
|
|
138
|
+
autocompletion=complete_rid,
|
|
139
|
+
),
|
|
140
|
+
profile: Optional[str] = typer.Option(
|
|
141
|
+
None, "--profile", help="Profile name", autocompletion=complete_profile
|
|
142
|
+
),
|
|
143
|
+
format: str = typer.Option(
|
|
144
|
+
"table",
|
|
145
|
+
"--format",
|
|
146
|
+
"-f",
|
|
147
|
+
help="Output format (table, json, csv)",
|
|
148
|
+
autocompletion=complete_output_format,
|
|
149
|
+
),
|
|
150
|
+
output: Optional[str] = typer.Option(
|
|
151
|
+
None, "--output", "-o", help="Output file path"
|
|
152
|
+
),
|
|
153
|
+
page_size: Optional[int] = typer.Option(
|
|
154
|
+
None, "--page-size", help="Number of items per page"
|
|
155
|
+
),
|
|
156
|
+
):
|
|
157
|
+
"""List all child resources of a folder."""
|
|
158
|
+
try:
|
|
159
|
+
# Cache the RID for future completions
|
|
160
|
+
cache_rid(folder_rid)
|
|
161
|
+
|
|
162
|
+
service = FolderService(profile=profile)
|
|
163
|
+
|
|
164
|
+
with SpinnerProgressTracker().track_spinner(
|
|
165
|
+
f"Listing children of folder {folder_rid}..."
|
|
166
|
+
):
|
|
167
|
+
children = service.list_children(folder_rid, page_size=page_size)
|
|
168
|
+
|
|
169
|
+
if not children:
|
|
170
|
+
formatter.print_info("No children found in this folder.")
|
|
171
|
+
return
|
|
172
|
+
|
|
173
|
+
# Format output
|
|
174
|
+
if format == "json":
|
|
175
|
+
if output:
|
|
176
|
+
formatter.save_to_file(children, output, "json")
|
|
177
|
+
else:
|
|
178
|
+
formatter.format_list(children)
|
|
179
|
+
elif format == "csv":
|
|
180
|
+
if output:
|
|
181
|
+
formatter.save_to_file(children, output, "csv")
|
|
182
|
+
else:
|
|
183
|
+
formatter.format_list(children)
|
|
184
|
+
else:
|
|
185
|
+
_format_children_table(children)
|
|
186
|
+
|
|
187
|
+
if output:
|
|
188
|
+
formatter.print_success(f"Folder children saved to {output}")
|
|
189
|
+
|
|
190
|
+
except (ProfileNotFoundError, MissingCredentialsError) as e:
|
|
191
|
+
formatter.print_error(f"Authentication error: {e}")
|
|
192
|
+
raise typer.Exit(1)
|
|
193
|
+
except Exception as e:
|
|
194
|
+
formatter.print_error(f"Failed to list folder children: {e}")
|
|
195
|
+
raise typer.Exit(1)
|
|
196
|
+
|
|
197
|
+
|
|
198
|
+
@app.command("batch-get")
|
|
199
|
+
def get_folders_batch(
|
|
200
|
+
folder_rids: List[str] = typer.Argument(
|
|
201
|
+
..., help="Folder Resource Identifiers (space-separated)"
|
|
202
|
+
),
|
|
203
|
+
profile: Optional[str] = typer.Option(
|
|
204
|
+
None, "--profile", help="Profile name", autocompletion=complete_profile
|
|
205
|
+
),
|
|
206
|
+
format: str = typer.Option(
|
|
207
|
+
"table",
|
|
208
|
+
"--format",
|
|
209
|
+
"-f",
|
|
210
|
+
help="Output format (table, json, csv)",
|
|
211
|
+
autocompletion=complete_output_format,
|
|
212
|
+
),
|
|
213
|
+
output: Optional[str] = typer.Option(
|
|
214
|
+
None, "--output", "-o", help="Output file path"
|
|
215
|
+
),
|
|
216
|
+
):
|
|
217
|
+
"""Get multiple folders in a single request (max 1000)."""
|
|
218
|
+
try:
|
|
219
|
+
service = FolderService(profile=profile)
|
|
220
|
+
|
|
221
|
+
with SpinnerProgressTracker().track_spinner(
|
|
222
|
+
f"Fetching {len(folder_rids)} folders..."
|
|
223
|
+
):
|
|
224
|
+
folders = service.get_folders_batch(folder_rids)
|
|
225
|
+
|
|
226
|
+
# Cache RIDs for future completions
|
|
227
|
+
for folder in folders:
|
|
228
|
+
if folder.get("rid"):
|
|
229
|
+
cache_rid(folder["rid"])
|
|
230
|
+
|
|
231
|
+
# Format output
|
|
232
|
+
if format == "json":
|
|
233
|
+
if output:
|
|
234
|
+
formatter.save_to_file(folders, output, "json")
|
|
235
|
+
else:
|
|
236
|
+
formatter.format_list(folders)
|
|
237
|
+
elif format == "csv":
|
|
238
|
+
if output:
|
|
239
|
+
formatter.save_to_file(folders, output, "csv")
|
|
240
|
+
else:
|
|
241
|
+
formatter.format_list(folders)
|
|
242
|
+
else:
|
|
243
|
+
_format_folders_batch_table(folders)
|
|
244
|
+
|
|
245
|
+
if output:
|
|
246
|
+
formatter.print_success(f"Folders information saved to {output}")
|
|
247
|
+
|
|
248
|
+
except (ProfileNotFoundError, MissingCredentialsError) as e:
|
|
249
|
+
formatter.print_error(f"Authentication error: {e}")
|
|
250
|
+
raise typer.Exit(1)
|
|
251
|
+
except ValueError as e:
|
|
252
|
+
formatter.print_error(f"Invalid request: {e}")
|
|
253
|
+
raise typer.Exit(1)
|
|
254
|
+
except Exception as e:
|
|
255
|
+
formatter.print_error(f"Failed to get folders batch: {e}")
|
|
256
|
+
raise typer.Exit(1)
|
|
257
|
+
|
|
258
|
+
|
|
259
|
+
def _format_folder_table(folder: dict):
|
|
260
|
+
"""Format folder information as a table."""
|
|
261
|
+
table = Table(
|
|
262
|
+
title="Folder Information", show_header=True, header_style="bold cyan"
|
|
263
|
+
)
|
|
264
|
+
table.add_column("Property", style="cyan")
|
|
265
|
+
table.add_column("Value")
|
|
266
|
+
|
|
267
|
+
table.add_row("RID", folder.get("rid", "N/A"))
|
|
268
|
+
table.add_row("Display Name", folder.get("display_name", "N/A"))
|
|
269
|
+
table.add_row("Description", folder.get("description", "N/A"))
|
|
270
|
+
table.add_row("Parent Folder", folder.get("parent_folder_rid", "N/A"))
|
|
271
|
+
table.add_row("Created", folder.get("created", "N/A"))
|
|
272
|
+
table.add_row("Modified", folder.get("modified", "N/A"))
|
|
273
|
+
|
|
274
|
+
console.print(table)
|
|
275
|
+
|
|
276
|
+
|
|
277
|
+
def _format_children_table(children: List[dict]):
|
|
278
|
+
"""Format folder children as a table."""
|
|
279
|
+
table = Table(title="Folder Children", show_header=True, header_style="bold cyan")
|
|
280
|
+
table.add_column("Type", style="cyan")
|
|
281
|
+
table.add_column("Display Name")
|
|
282
|
+
table.add_column("RID")
|
|
283
|
+
|
|
284
|
+
for child in children:
|
|
285
|
+
table.add_row(
|
|
286
|
+
child.get("type", "unknown"),
|
|
287
|
+
child.get("display_name", child.get("name", "N/A")),
|
|
288
|
+
child.get("rid", "N/A"),
|
|
289
|
+
)
|
|
290
|
+
|
|
291
|
+
console.print(table)
|
|
292
|
+
console.print(f"\nTotal: {len(children)} items")
|
|
293
|
+
|
|
294
|
+
|
|
295
|
+
def _format_folders_batch_table(folders: List[dict]):
|
|
296
|
+
"""Format multiple folders as a table."""
|
|
297
|
+
table = Table(title="Folders", show_header=True, header_style="bold cyan")
|
|
298
|
+
table.add_column("Display Name")
|
|
299
|
+
table.add_column("RID")
|
|
300
|
+
table.add_column("Parent Folder")
|
|
301
|
+
table.add_column("Description")
|
|
302
|
+
|
|
303
|
+
for folder in folders:
|
|
304
|
+
table.add_row(
|
|
305
|
+
folder.get("display_name", "N/A"),
|
|
306
|
+
folder.get("rid", "N/A"),
|
|
307
|
+
folder.get("parent_folder_rid", "N/A"),
|
|
308
|
+
folder.get("description", "N/A") or "",
|
|
309
|
+
)
|
|
310
|
+
|
|
311
|
+
console.print(table)
|
|
312
|
+
console.print(f"\nTotal: {len(folders)} folders")
|
|
313
|
+
|
|
314
|
+
|
|
315
|
+
@app.callback()
|
|
316
|
+
def main():
|
|
317
|
+
"""
|
|
318
|
+
Folder operations using foundry-platform-sdk.
|
|
319
|
+
|
|
320
|
+
Manage folders in the Foundry filesystem. Create, retrieve, and list
|
|
321
|
+
folder contents using Resource Identifiers (RIDs).
|
|
322
|
+
|
|
323
|
+
The root folder RID is: ri.compass.main.folder.0
|
|
324
|
+
|
|
325
|
+
Examples:
|
|
326
|
+
# Create a folder in root
|
|
327
|
+
pltr folder create "My Folder"
|
|
328
|
+
|
|
329
|
+
# Create a folder in a specific parent
|
|
330
|
+
pltr folder create "Sub Folder" --parent-folder ri.compass.main.folder.xyz123
|
|
331
|
+
|
|
332
|
+
# List root folder contents
|
|
333
|
+
pltr folder list ri.compass.main.folder.0
|
|
334
|
+
|
|
335
|
+
# Get folder information
|
|
336
|
+
pltr folder get ri.compass.main.folder.xyz123
|
|
337
|
+
"""
|
|
338
|
+
pass
|
pltr/commands/ontology.py
CHANGED
|
@@ -33,16 +33,13 @@ def list_ontologies(
|
|
|
33
33
|
output: Optional[str] = typer.Option(
|
|
34
34
|
None, "--output", "-o", help="Output file path"
|
|
35
35
|
),
|
|
36
|
-
page_size: Optional[int] = typer.Option(
|
|
37
|
-
None, "--page-size", help="Number of results per page"
|
|
38
|
-
),
|
|
39
36
|
):
|
|
40
37
|
"""List all available ontologies."""
|
|
41
38
|
try:
|
|
42
39
|
service = OntologyService(profile=profile)
|
|
43
40
|
|
|
44
41
|
with SpinnerProgressTracker().track_spinner("Fetching ontologies..."):
|
|
45
|
-
ontologies = service.list_ontologies(
|
|
42
|
+
ontologies = service.list_ontologies()
|
|
46
43
|
|
|
47
44
|
formatter.format_table(
|
|
48
45
|
ontologies,
|
|
@@ -106,16 +103,13 @@ def list_object_types(
|
|
|
106
103
|
output: Optional[str] = typer.Option(
|
|
107
104
|
None, "--output", "-o", help="Output file path"
|
|
108
105
|
),
|
|
109
|
-
page_size: Optional[int] = typer.Option(
|
|
110
|
-
None, "--page-size", help="Number of results per page"
|
|
111
|
-
),
|
|
112
106
|
):
|
|
113
107
|
"""List object types in an ontology."""
|
|
114
108
|
try:
|
|
115
109
|
service = ObjectTypeService(profile=profile)
|
|
116
110
|
|
|
117
111
|
with SpinnerProgressTracker().track_spinner("Fetching object types..."):
|
|
118
|
-
object_types = service.list_object_types(ontology_rid
|
|
112
|
+
object_types = service.list_object_types(ontology_rid)
|
|
119
113
|
|
|
120
114
|
formatter.format_table(
|
|
121
115
|
object_types,
|
pltr/config/aliases.py
ADDED
|
@@ -0,0 +1,254 @@
|
|
|
1
|
+
"""Alias configuration management for pltr-cli."""
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
from typing import Dict, List, Optional
|
|
5
|
+
|
|
6
|
+
from rich import print as rprint
|
|
7
|
+
from rich.table import Table
|
|
8
|
+
|
|
9
|
+
from pltr.config.settings import Settings
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class AliasManager:
|
|
13
|
+
"""Manages command aliases for pltr-cli."""
|
|
14
|
+
|
|
15
|
+
def __init__(self) -> None:
|
|
16
|
+
"""Initialize the alias manager."""
|
|
17
|
+
settings = Settings()
|
|
18
|
+
self.config_dir = settings.config_dir
|
|
19
|
+
self.aliases_file = self.config_dir / "aliases.json"
|
|
20
|
+
self.aliases = self._load_aliases()
|
|
21
|
+
|
|
22
|
+
def _load_aliases(self) -> Dict[str, str]:
|
|
23
|
+
"""Load aliases from the configuration file.
|
|
24
|
+
|
|
25
|
+
Returns:
|
|
26
|
+
Dictionary mapping alias names to commands
|
|
27
|
+
"""
|
|
28
|
+
if not self.aliases_file.exists():
|
|
29
|
+
return {}
|
|
30
|
+
|
|
31
|
+
try:
|
|
32
|
+
with open(self.aliases_file, "r") as f:
|
|
33
|
+
return json.load(f)
|
|
34
|
+
except (json.JSONDecodeError, IOError):
|
|
35
|
+
return {}
|
|
36
|
+
|
|
37
|
+
def _save_aliases(self) -> None:
|
|
38
|
+
"""Save aliases to the configuration file."""
|
|
39
|
+
self.config_dir.mkdir(parents=True, exist_ok=True)
|
|
40
|
+
with open(self.aliases_file, "w") as f:
|
|
41
|
+
json.dump(self.aliases, f, indent=2, sort_keys=True)
|
|
42
|
+
|
|
43
|
+
def add_alias(self, name: str, command: str) -> bool:
|
|
44
|
+
"""Add a new alias.
|
|
45
|
+
|
|
46
|
+
Args:
|
|
47
|
+
name: Alias name
|
|
48
|
+
command: Command to alias
|
|
49
|
+
|
|
50
|
+
Returns:
|
|
51
|
+
True if alias was added, False if it already exists
|
|
52
|
+
|
|
53
|
+
Raises:
|
|
54
|
+
ValueError: If the alias would create a circular reference
|
|
55
|
+
"""
|
|
56
|
+
if name in self.aliases:
|
|
57
|
+
return False
|
|
58
|
+
|
|
59
|
+
# Check for circular references
|
|
60
|
+
if self._would_create_cycle(name, command):
|
|
61
|
+
raise ValueError(f"Alias '{name}' would create a circular reference")
|
|
62
|
+
|
|
63
|
+
self.aliases[name] = command
|
|
64
|
+
self._save_aliases()
|
|
65
|
+
return True
|
|
66
|
+
|
|
67
|
+
def remove_alias(self, name: str) -> bool:
|
|
68
|
+
"""Remove an alias.
|
|
69
|
+
|
|
70
|
+
Args:
|
|
71
|
+
name: Alias name to remove
|
|
72
|
+
|
|
73
|
+
Returns:
|
|
74
|
+
True if alias was removed, False if it didn't exist
|
|
75
|
+
"""
|
|
76
|
+
if name not in self.aliases:
|
|
77
|
+
return False
|
|
78
|
+
|
|
79
|
+
del self.aliases[name]
|
|
80
|
+
self._save_aliases()
|
|
81
|
+
return True
|
|
82
|
+
|
|
83
|
+
def edit_alias(self, name: str, command: str) -> bool:
|
|
84
|
+
"""Edit an existing alias.
|
|
85
|
+
|
|
86
|
+
Args:
|
|
87
|
+
name: Alias name to edit
|
|
88
|
+
command: New command for the alias
|
|
89
|
+
|
|
90
|
+
Returns:
|
|
91
|
+
True if alias was edited, False if it doesn't exist
|
|
92
|
+
"""
|
|
93
|
+
if name not in self.aliases:
|
|
94
|
+
return False
|
|
95
|
+
|
|
96
|
+
# Check for circular references
|
|
97
|
+
if self._would_create_cycle(name, command):
|
|
98
|
+
raise ValueError(f"Alias '{name}' would create a circular reference")
|
|
99
|
+
|
|
100
|
+
self.aliases[name] = command
|
|
101
|
+
self._save_aliases()
|
|
102
|
+
return True
|
|
103
|
+
|
|
104
|
+
def get_alias(self, name: str) -> Optional[str]:
|
|
105
|
+
"""Get the command for an alias.
|
|
106
|
+
|
|
107
|
+
Args:
|
|
108
|
+
name: Alias name
|
|
109
|
+
|
|
110
|
+
Returns:
|
|
111
|
+
The aliased command, or None if alias doesn't exist
|
|
112
|
+
"""
|
|
113
|
+
return self.aliases.get(name)
|
|
114
|
+
|
|
115
|
+
def list_aliases(self) -> Dict[str, str]:
|
|
116
|
+
"""Get all aliases.
|
|
117
|
+
|
|
118
|
+
Returns:
|
|
119
|
+
Dictionary of all aliases
|
|
120
|
+
"""
|
|
121
|
+
return self.aliases.copy()
|
|
122
|
+
|
|
123
|
+
def resolve_alias(self, command: str, max_depth: int = 10) -> str:
|
|
124
|
+
"""Resolve an alias to its final command.
|
|
125
|
+
|
|
126
|
+
Args:
|
|
127
|
+
command: Command that might be an alias
|
|
128
|
+
max_depth: Maximum recursion depth for nested aliases
|
|
129
|
+
|
|
130
|
+
Returns:
|
|
131
|
+
The resolved command
|
|
132
|
+
"""
|
|
133
|
+
resolved = command
|
|
134
|
+
depth = 0
|
|
135
|
+
seen = set()
|
|
136
|
+
|
|
137
|
+
while resolved in self.aliases and depth < max_depth:
|
|
138
|
+
if resolved in seen:
|
|
139
|
+
# Circular reference detected
|
|
140
|
+
return command
|
|
141
|
+
|
|
142
|
+
seen.add(resolved)
|
|
143
|
+
resolved = self.aliases[resolved]
|
|
144
|
+
depth += 1
|
|
145
|
+
|
|
146
|
+
return resolved
|
|
147
|
+
|
|
148
|
+
def _would_create_cycle(self, name: str, command: str) -> bool:
|
|
149
|
+
"""Check if adding/editing an alias would create a cycle.
|
|
150
|
+
|
|
151
|
+
Args:
|
|
152
|
+
name: Alias name
|
|
153
|
+
command: Command to check
|
|
154
|
+
|
|
155
|
+
Returns:
|
|
156
|
+
True if this would create a cycle
|
|
157
|
+
"""
|
|
158
|
+
# Direct self-reference
|
|
159
|
+
if command == name:
|
|
160
|
+
return True
|
|
161
|
+
|
|
162
|
+
# Follow the chain starting from command
|
|
163
|
+
# If we ever reach 'name', it would create a cycle
|
|
164
|
+
current = command
|
|
165
|
+
visited = set()
|
|
166
|
+
|
|
167
|
+
while current in self.aliases:
|
|
168
|
+
# Check for existing cycles
|
|
169
|
+
if current in visited:
|
|
170
|
+
break
|
|
171
|
+
visited.add(current)
|
|
172
|
+
|
|
173
|
+
# Get what this alias points to
|
|
174
|
+
current = self.aliases[current]
|
|
175
|
+
|
|
176
|
+
# If we reached the name we're trying to add, it's a cycle
|
|
177
|
+
if current == name:
|
|
178
|
+
return True
|
|
179
|
+
|
|
180
|
+
return False
|
|
181
|
+
|
|
182
|
+
def display_aliases(self, name: Optional[str] = None) -> None:
|
|
183
|
+
"""Display aliases in a formatted table.
|
|
184
|
+
|
|
185
|
+
Args:
|
|
186
|
+
name: Optional specific alias to display
|
|
187
|
+
"""
|
|
188
|
+
if name:
|
|
189
|
+
command = self.get_alias(name)
|
|
190
|
+
if command:
|
|
191
|
+
rprint(f"[green]{name}[/green] → {command}")
|
|
192
|
+
else:
|
|
193
|
+
rprint(f"[red]Alias '{name}' not found[/red]")
|
|
194
|
+
return
|
|
195
|
+
|
|
196
|
+
if not self.aliases:
|
|
197
|
+
rprint("[yellow]No aliases configured[/yellow]")
|
|
198
|
+
return
|
|
199
|
+
|
|
200
|
+
table = Table(title="Command Aliases", show_header=True)
|
|
201
|
+
table.add_column("Alias", style="cyan")
|
|
202
|
+
table.add_column("Command", style="green")
|
|
203
|
+
|
|
204
|
+
for alias_name, command in sorted(self.aliases.items()):
|
|
205
|
+
table.add_row(alias_name, command)
|
|
206
|
+
|
|
207
|
+
rprint(table)
|
|
208
|
+
|
|
209
|
+
def get_completion_items(self) -> List[str]:
|
|
210
|
+
"""Get alias names for shell completion.
|
|
211
|
+
|
|
212
|
+
Returns:
|
|
213
|
+
List of alias names
|
|
214
|
+
"""
|
|
215
|
+
return list(self.aliases.keys())
|
|
216
|
+
|
|
217
|
+
def clear_all(self) -> int:
|
|
218
|
+
"""Clear all aliases.
|
|
219
|
+
|
|
220
|
+
Returns:
|
|
221
|
+
Number of aliases cleared
|
|
222
|
+
"""
|
|
223
|
+
count = len(self.aliases)
|
|
224
|
+
self.aliases = {}
|
|
225
|
+
self._save_aliases()
|
|
226
|
+
return count
|
|
227
|
+
|
|
228
|
+
def import_aliases(self, data: Dict[str, str]) -> int:
|
|
229
|
+
"""Import aliases from a dictionary.
|
|
230
|
+
|
|
231
|
+
Args:
|
|
232
|
+
data: Dictionary of aliases to import
|
|
233
|
+
|
|
234
|
+
Returns:
|
|
235
|
+
Number of aliases imported
|
|
236
|
+
"""
|
|
237
|
+
count = 0
|
|
238
|
+
for name, command in data.items():
|
|
239
|
+
if not self._would_create_cycle(name, command):
|
|
240
|
+
self.aliases[name] = command
|
|
241
|
+
count += 1
|
|
242
|
+
|
|
243
|
+
if count > 0:
|
|
244
|
+
self._save_aliases()
|
|
245
|
+
|
|
246
|
+
return count
|
|
247
|
+
|
|
248
|
+
def export_aliases(self) -> Dict[str, str]:
|
|
249
|
+
"""Export all aliases.
|
|
250
|
+
|
|
251
|
+
Returns:
|
|
252
|
+
Dictionary of all aliases
|
|
253
|
+
"""
|
|
254
|
+
return self.aliases.copy()
|
pltr/services/folder.py
ADDED
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Folder service wrapper for Foundry SDK filesystem API.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from typing import Any, Optional, Dict, List
|
|
6
|
+
|
|
7
|
+
from .base import BaseService
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class FolderService(BaseService):
|
|
11
|
+
"""Service wrapper for Foundry folder operations using filesystem API."""
|
|
12
|
+
|
|
13
|
+
def _get_service(self) -> Any:
|
|
14
|
+
"""Get the Foundry filesystem service."""
|
|
15
|
+
return self.client.filesystem
|
|
16
|
+
|
|
17
|
+
def create_folder(
|
|
18
|
+
self, display_name: str, parent_folder_rid: str
|
|
19
|
+
) -> Dict[str, Any]:
|
|
20
|
+
"""
|
|
21
|
+
Create a new folder.
|
|
22
|
+
|
|
23
|
+
Args:
|
|
24
|
+
display_name: Folder display name
|
|
25
|
+
parent_folder_rid: Parent folder RID (use 'ri.compass.main.folder.0' for root)
|
|
26
|
+
|
|
27
|
+
Returns:
|
|
28
|
+
Created folder information
|
|
29
|
+
"""
|
|
30
|
+
try:
|
|
31
|
+
folder = self.service.Folder.create(
|
|
32
|
+
display_name=display_name,
|
|
33
|
+
parent_folder_rid=parent_folder_rid,
|
|
34
|
+
preview=True,
|
|
35
|
+
)
|
|
36
|
+
return self._format_folder_info(folder)
|
|
37
|
+
except Exception as e:
|
|
38
|
+
raise RuntimeError(f"Failed to create folder '{display_name}': {e}")
|
|
39
|
+
|
|
40
|
+
def get_folder(self, folder_rid: str) -> Dict[str, Any]:
|
|
41
|
+
"""
|
|
42
|
+
Get information about a specific folder.
|
|
43
|
+
|
|
44
|
+
Args:
|
|
45
|
+
folder_rid: Folder Resource Identifier
|
|
46
|
+
|
|
47
|
+
Returns:
|
|
48
|
+
Folder information dictionary
|
|
49
|
+
"""
|
|
50
|
+
try:
|
|
51
|
+
folder = self.service.Folder.get(folder_rid, preview=True)
|
|
52
|
+
return self._format_folder_info(folder)
|
|
53
|
+
except Exception as e:
|
|
54
|
+
raise RuntimeError(f"Failed to get folder {folder_rid}: {e}")
|
|
55
|
+
|
|
56
|
+
def list_children(
|
|
57
|
+
self,
|
|
58
|
+
folder_rid: str,
|
|
59
|
+
page_size: Optional[int] = None,
|
|
60
|
+
page_token: Optional[str] = None,
|
|
61
|
+
) -> List[Dict[str, Any]]:
|
|
62
|
+
"""
|
|
63
|
+
List all child resources of a folder.
|
|
64
|
+
|
|
65
|
+
Args:
|
|
66
|
+
folder_rid: Folder Resource Identifier
|
|
67
|
+
page_size: Number of items per page (optional)
|
|
68
|
+
page_token: Pagination token (optional)
|
|
69
|
+
|
|
70
|
+
Returns:
|
|
71
|
+
List of child resources
|
|
72
|
+
"""
|
|
73
|
+
try:
|
|
74
|
+
children = []
|
|
75
|
+
# The children method returns an iterator
|
|
76
|
+
for child in self.service.Folder.children(
|
|
77
|
+
folder_rid, page_size=page_size, page_token=page_token, preview=True
|
|
78
|
+
):
|
|
79
|
+
children.append(self._format_resource_info(child))
|
|
80
|
+
return children
|
|
81
|
+
except Exception as e:
|
|
82
|
+
raise RuntimeError(f"Failed to list children of folder {folder_rid}: {e}")
|
|
83
|
+
|
|
84
|
+
def get_folders_batch(self, folder_rids: List[str]) -> List[Dict[str, Any]]:
|
|
85
|
+
"""
|
|
86
|
+
Get multiple folders in a single request.
|
|
87
|
+
|
|
88
|
+
Args:
|
|
89
|
+
folder_rids: List of folder RIDs (max 1000)
|
|
90
|
+
|
|
91
|
+
Returns:
|
|
92
|
+
List of folder information dictionaries
|
|
93
|
+
"""
|
|
94
|
+
if len(folder_rids) > 1000:
|
|
95
|
+
raise ValueError("Maximum batch size is 1000 folders")
|
|
96
|
+
|
|
97
|
+
try:
|
|
98
|
+
response = self.service.Folder.get_batch(body=folder_rids, preview=True)
|
|
99
|
+
folders = []
|
|
100
|
+
for folder in response.folders:
|
|
101
|
+
folders.append(self._format_folder_info(folder))
|
|
102
|
+
return folders
|
|
103
|
+
except Exception as e:
|
|
104
|
+
raise RuntimeError(f"Failed to get folders batch: {e}")
|
|
105
|
+
|
|
106
|
+
def _format_folder_info(self, folder: Any) -> Dict[str, Any]:
|
|
107
|
+
"""
|
|
108
|
+
Format folder information for consistent output.
|
|
109
|
+
|
|
110
|
+
Args:
|
|
111
|
+
folder: Folder object from Foundry SDK
|
|
112
|
+
|
|
113
|
+
Returns:
|
|
114
|
+
Formatted folder information dictionary
|
|
115
|
+
"""
|
|
116
|
+
return {
|
|
117
|
+
"rid": getattr(folder, "rid", None),
|
|
118
|
+
"display_name": getattr(folder, "display_name", None),
|
|
119
|
+
"description": getattr(folder, "description", None),
|
|
120
|
+
"created": self._format_timestamp(getattr(folder, "created", None)),
|
|
121
|
+
"modified": self._format_timestamp(getattr(folder, "modified", None)),
|
|
122
|
+
"parent_folder_rid": getattr(folder, "parent_folder_rid", None),
|
|
123
|
+
"type": "folder",
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
def _format_resource_info(self, resource: Any) -> Dict[str, Any]:
|
|
127
|
+
"""
|
|
128
|
+
Format resource information (can be folder, dataset, etc.).
|
|
129
|
+
|
|
130
|
+
Args:
|
|
131
|
+
resource: Resource object from Foundry SDK
|
|
132
|
+
|
|
133
|
+
Returns:
|
|
134
|
+
Formatted resource information dictionary
|
|
135
|
+
"""
|
|
136
|
+
resource_type = getattr(resource, "type", "unknown")
|
|
137
|
+
base_info = {
|
|
138
|
+
"rid": getattr(resource, "rid", None),
|
|
139
|
+
"display_name": getattr(resource, "display_name", None),
|
|
140
|
+
"type": resource_type,
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
# Add type-specific fields
|
|
144
|
+
if resource_type == "folder":
|
|
145
|
+
base_info["description"] = getattr(resource, "description", None)
|
|
146
|
+
elif resource_type == "dataset":
|
|
147
|
+
base_info["name"] = getattr(resource, "name", None)
|
|
148
|
+
|
|
149
|
+
return base_info
|
|
150
|
+
|
|
151
|
+
def _format_timestamp(self, timestamp: Any) -> Optional[str]:
|
|
152
|
+
"""
|
|
153
|
+
Format timestamp for display.
|
|
154
|
+
|
|
155
|
+
Args:
|
|
156
|
+
timestamp: Timestamp object from SDK
|
|
157
|
+
|
|
158
|
+
Returns:
|
|
159
|
+
Formatted timestamp string or None
|
|
160
|
+
"""
|
|
161
|
+
if timestamp is None:
|
|
162
|
+
return None
|
|
163
|
+
|
|
164
|
+
# Handle different timestamp formats from the SDK
|
|
165
|
+
if hasattr(timestamp, "time"):
|
|
166
|
+
return str(timestamp.time)
|
|
167
|
+
return str(timestamp)
|
pltr/services/ontology.py
CHANGED
|
@@ -13,20 +13,18 @@ class OntologyService(BaseService):
|
|
|
13
13
|
"""Get the Foundry ontologies service."""
|
|
14
14
|
return self.client.ontologies
|
|
15
15
|
|
|
16
|
-
def list_ontologies(self
|
|
16
|
+
def list_ontologies(self) -> List[Dict[str, Any]]:
|
|
17
17
|
"""
|
|
18
18
|
List all ontologies visible to the current user.
|
|
19
19
|
|
|
20
|
-
Args:
|
|
21
|
-
page_size: Number of results per page
|
|
22
|
-
|
|
23
20
|
Returns:
|
|
24
21
|
List of ontology information dictionaries
|
|
25
22
|
"""
|
|
26
23
|
try:
|
|
27
|
-
result = self.service.Ontology.list(
|
|
24
|
+
result = self.service.Ontology.list()
|
|
28
25
|
ontologies = []
|
|
29
|
-
|
|
26
|
+
# The response has a 'data' field containing the list of ontologies
|
|
27
|
+
for ontology in result.data:
|
|
30
28
|
ontologies.append(self._format_ontology_info(ontology))
|
|
31
29
|
return ontologies
|
|
32
30
|
except Exception as e:
|
|
@@ -65,23 +63,22 @@ class ObjectTypeService(BaseService):
|
|
|
65
63
|
"""Get the Foundry ontologies service."""
|
|
66
64
|
return self.client.ontologies
|
|
67
65
|
|
|
68
|
-
def list_object_types(
|
|
69
|
-
self, ontology_rid: str, page_size: Optional[int] = None
|
|
70
|
-
) -> List[Dict[str, Any]]:
|
|
66
|
+
def list_object_types(self, ontology_rid: str) -> List[Dict[str, Any]]:
|
|
71
67
|
"""
|
|
72
68
|
List object types in an ontology.
|
|
73
69
|
|
|
74
70
|
Args:
|
|
75
71
|
ontology_rid: Ontology Resource Identifier
|
|
76
|
-
page_size: Number of results per page
|
|
77
72
|
|
|
78
73
|
Returns:
|
|
79
74
|
List of object type information dictionaries
|
|
80
75
|
"""
|
|
81
76
|
try:
|
|
82
|
-
|
|
77
|
+
# ObjectType is nested under Ontology in the SDK
|
|
78
|
+
result = self.service.Ontology.ObjectType.list(ontology_rid)
|
|
83
79
|
object_types = []
|
|
84
|
-
|
|
80
|
+
# The response has a 'data' field containing the list of object types
|
|
81
|
+
for obj_type in result.data:
|
|
85
82
|
object_types.append(self._format_object_type_info(obj_type))
|
|
86
83
|
return object_types
|
|
87
84
|
except Exception as e:
|
|
@@ -99,13 +96,14 @@ class ObjectTypeService(BaseService):
|
|
|
99
96
|
Object type information dictionary
|
|
100
97
|
"""
|
|
101
98
|
try:
|
|
102
|
-
|
|
99
|
+
# ObjectType is nested under Ontology in the SDK
|
|
100
|
+
obj_type = self.service.Ontology.ObjectType.get(ontology_rid, object_type)
|
|
103
101
|
return self._format_object_type_info(obj_type)
|
|
104
102
|
except Exception as e:
|
|
105
103
|
raise RuntimeError(f"Failed to get object type {object_type}: {e}")
|
|
106
104
|
|
|
107
105
|
def list_outgoing_link_types(
|
|
108
|
-
self, ontology_rid: str, object_type: str
|
|
106
|
+
self, ontology_rid: str, object_type: str
|
|
109
107
|
) -> List[Dict[str, Any]]:
|
|
110
108
|
"""
|
|
111
109
|
List outgoing link types for an object type.
|
|
@@ -113,17 +111,18 @@ class ObjectTypeService(BaseService):
|
|
|
113
111
|
Args:
|
|
114
112
|
ontology_rid: Ontology Resource Identifier
|
|
115
113
|
object_type: Object type API name
|
|
116
|
-
page_size: Number of results per page
|
|
117
114
|
|
|
118
115
|
Returns:
|
|
119
116
|
List of link type information dictionaries
|
|
120
117
|
"""
|
|
121
118
|
try:
|
|
122
|
-
|
|
123
|
-
|
|
119
|
+
# ObjectType is nested under Ontology in the SDK
|
|
120
|
+
result = self.service.Ontology.ObjectType.list_outgoing_link_types(
|
|
121
|
+
ontology_rid, object_type
|
|
124
122
|
)
|
|
125
123
|
link_types = []
|
|
126
|
-
|
|
124
|
+
# The response has a 'data' field containing the list of link types
|
|
125
|
+
for link_type in result.data:
|
|
127
126
|
link_types.append(self._format_link_type_info(link_type))
|
|
128
127
|
return link_types
|
|
129
128
|
except Exception as e:
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
"""Alias resolution utilities for CLI commands."""
|
|
2
|
+
|
|
3
|
+
import shlex
|
|
4
|
+
import sys
|
|
5
|
+
from typing import List, Optional
|
|
6
|
+
|
|
7
|
+
from pltr.config.aliases import AliasManager
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def resolve_command_aliases(args: Optional[List[str]] = None) -> List[str]:
|
|
11
|
+
"""Resolve command aliases in the argument list.
|
|
12
|
+
|
|
13
|
+
Args:
|
|
14
|
+
args: Command arguments (defaults to sys.argv[1:])
|
|
15
|
+
|
|
16
|
+
Returns:
|
|
17
|
+
Resolved command arguments
|
|
18
|
+
"""
|
|
19
|
+
if args is None:
|
|
20
|
+
args = sys.argv[1:]
|
|
21
|
+
|
|
22
|
+
if not args:
|
|
23
|
+
return args
|
|
24
|
+
|
|
25
|
+
# Don't resolve aliases for certain commands
|
|
26
|
+
if args[0] in ["alias", "--help", "-h", "--version", "completion"]:
|
|
27
|
+
return args
|
|
28
|
+
|
|
29
|
+
# Check if the first argument is an alias
|
|
30
|
+
manager = AliasManager()
|
|
31
|
+
first_arg = args[0]
|
|
32
|
+
|
|
33
|
+
# Try to resolve as an alias
|
|
34
|
+
resolved = manager.resolve_alias(first_arg)
|
|
35
|
+
|
|
36
|
+
if resolved != first_arg:
|
|
37
|
+
# It's an alias - parse the resolved command
|
|
38
|
+
try:
|
|
39
|
+
resolved_parts = shlex.split(resolved)
|
|
40
|
+
# Replace the first argument with the resolved command parts
|
|
41
|
+
# and append any additional arguments
|
|
42
|
+
return resolved_parts + args[1:]
|
|
43
|
+
except ValueError:
|
|
44
|
+
# If parsing fails, return original args
|
|
45
|
+
return args
|
|
46
|
+
|
|
47
|
+
return args
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def inject_alias_resolution() -> None:
|
|
51
|
+
"""Inject alias resolution into sys.argv before CLI parsing."""
|
|
52
|
+
# Get resolved arguments
|
|
53
|
+
resolved_args = resolve_command_aliases()
|
|
54
|
+
|
|
55
|
+
# Replace sys.argv with resolved arguments
|
|
56
|
+
sys.argv = [sys.argv[0]] + resolved_args
|
pltr/utils/completion.py
CHANGED
|
@@ -6,6 +6,7 @@ from pathlib import Path
|
|
|
6
6
|
import json
|
|
7
7
|
|
|
8
8
|
from pltr.config.profiles import ProfileManager
|
|
9
|
+
from pltr.config.aliases import AliasManager
|
|
9
10
|
|
|
10
11
|
|
|
11
12
|
def get_cached_rids() -> List[str]:
|
|
@@ -111,6 +112,13 @@ def complete_ontology_action(incomplete: str):
|
|
|
111
112
|
return [action for action in actions if action.startswith(incomplete)]
|
|
112
113
|
|
|
113
114
|
|
|
115
|
+
def complete_alias_names(incomplete: str):
|
|
116
|
+
"""Complete alias names."""
|
|
117
|
+
manager = AliasManager()
|
|
118
|
+
aliases = manager.get_completion_items()
|
|
119
|
+
return [alias for alias in aliases if alias.startswith(incomplete)]
|
|
120
|
+
|
|
121
|
+
|
|
114
122
|
def complete_file_path(incomplete: str):
|
|
115
123
|
"""Complete file paths."""
|
|
116
124
|
# This is handled by shell natively, but we can provide hints
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: pltr-cli
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.4.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
|
|
@@ -37,7 +37,7 @@ Description-Content-Type: text/markdown
|
|
|
37
37
|
|
|
38
38
|
# pltr-cli
|
|
39
39
|
|
|
40
|
-
A comprehensive command-line interface for Palantir Foundry APIs, providing
|
|
40
|
+
A comprehensive command-line interface for Palantir Foundry APIs, providing 70+ commands for data analysis, ontology operations, SQL queries, folder management, and administrative tasks.
|
|
41
41
|
|
|
42
42
|
## Overview
|
|
43
43
|
|
|
@@ -47,6 +47,7 @@ A comprehensive command-line interface for Palantir Foundry APIs, providing 65+
|
|
|
47
47
|
|
|
48
48
|
- 🔐 **Secure Authentication**: Token and OAuth2 support with encrypted credential storage
|
|
49
49
|
- 📊 **Dataset Operations**: Get dataset information and create new datasets (RID-based API)
|
|
50
|
+
- 📁 **Folder Management**: Create, explore, and manage Foundry filesystem structure
|
|
50
51
|
- 🎯 **Comprehensive Ontology Access**: 13 commands for objects, actions, and queries
|
|
51
52
|
- 📝 **Full SQL Support**: Execute, submit, monitor, and export query results
|
|
52
53
|
- 👥 **Admin Operations**: User, group, role, and organization management (16 commands)
|
|
@@ -109,6 +110,12 @@ pltr admin user current
|
|
|
109
110
|
# List available ontologies
|
|
110
111
|
pltr ontology list
|
|
111
112
|
|
|
113
|
+
# Create a new folder
|
|
114
|
+
pltr folder create "My Project"
|
|
115
|
+
|
|
116
|
+
# List root folder contents
|
|
117
|
+
pltr folder list ri.compass.main.folder.0
|
|
118
|
+
|
|
112
119
|
# Execute a simple SQL query
|
|
113
120
|
pltr sql execute "SELECT 1 as test"
|
|
114
121
|
|
|
@@ -133,7 +140,7 @@ pltr-cli provides comprehensive documentation to help you get the most out of th
|
|
|
133
140
|
### 📖 User Guides
|
|
134
141
|
- **[Quick Start Guide](docs/user-guide/quick-start.md)** - Get up and running in 5 minutes
|
|
135
142
|
- **[Authentication Setup](docs/user-guide/authentication.md)** - Complete guide to token and OAuth2 setup
|
|
136
|
-
- **[Command Reference](docs/user-guide/commands.md)** - Complete reference for all
|
|
143
|
+
- **[Command Reference](docs/user-guide/commands.md)** - Complete reference for all 70+ commands
|
|
137
144
|
- **[Common Workflows](docs/user-guide/workflows.md)** - Real-world data analysis patterns
|
|
138
145
|
- **[Troubleshooting](docs/user-guide/troubleshooting.md)** - Solutions to common issues
|
|
139
146
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
pltr/__init__.py,sha256=kUR5RAFc7HCeiqdlX36dZOHkUI5wI6V_43RpEcD8b-0,22
|
|
2
|
-
pltr/__main__.py,sha256=
|
|
3
|
-
pltr/cli.py,sha256=
|
|
2
|
+
pltr/__main__.py,sha256=HWJ49UoAYBQCf8kjuySPmBTuUjTZrOx-y6PzMTyS1KE,879
|
|
3
|
+
pltr/cli.py,sha256=cogkxi-J2hKrWKzjRFy6GLvcsPfrE_r_pAG6EMmuJ8s,1955
|
|
4
4
|
pltr/auth/__init__.py,sha256=G0V-Rh25FaJsH2nhrf146XQQG_ApdbyPJNuHJC25kgk,38
|
|
5
5
|
pltr/auth/base.py,sha256=LvmCwS7A0q0CITcym8udPzdACL52_jSGusiaeCTOaE8,981
|
|
6
6
|
pltr/auth/manager.py,sha256=ZqlGefr1a8MGx0g7kkQhpmiuVp0XTg3f43yMBCk-IRo,4305
|
|
@@ -9,14 +9,17 @@ pltr/auth/storage.py,sha256=C7I3-22CJcnrKGNdxk9nXjphsnqQVguT5gNfAnR78Ok,2474
|
|
|
9
9
|
pltr/auth/token.py,sha256=V48kxGn7CFbNGo17er5oI_ZA3xJ3iS9TsFjphZYqS2s,1925
|
|
10
10
|
pltr/commands/__init__.py,sha256=iOLJ1ql4mz-3-4nz21hAqjd-9IjpYAIxr9SJQKHNFxM,29
|
|
11
11
|
pltr/commands/admin.py,sha256=foscSO-QuH6uggUR5Rmv9pTqGjEXTUzpmMFj2-8hEJs,17065
|
|
12
|
+
pltr/commands/alias.py,sha256=r9xMsQNrGvaixSlspzoO2IXQ44LFXuZM4itt8vC0dRc,6862
|
|
12
13
|
pltr/commands/completion.py,sha256=YTxaRL4-rDs5n7aXf3ogFsxbHVJUBo_HiBbd0fbBPZ0,10870
|
|
13
14
|
pltr/commands/configure.py,sha256=oYj-VlOEj3MDwtB2RC4bYOYzI_sXTanPnz7y1GmMTqY,4800
|
|
14
15
|
pltr/commands/dataset.py,sha256=BCYfaBpLji5wasOiH_jOqO-JC9ScfJhodox9kl9W2Cw,3609
|
|
15
|
-
pltr/commands/
|
|
16
|
+
pltr/commands/folder.py,sha256=IAPPA3Smk1IWqThneEtZ08Zp79vDKVUabSkL_nDvUWk,10679
|
|
17
|
+
pltr/commands/ontology.py,sha256=zUgSrmv8xi26SQK7GsM3qusgR9Wuka0GyzE7L8DkduE,18317
|
|
16
18
|
pltr/commands/shell.py,sha256=QLF7TEGpaau9i0A9s3VjnegvtHde-SLRqI4suJFT4WI,3622
|
|
17
19
|
pltr/commands/sql.py,sha256=wol0Rlvi_RplCFbOg4LCa3VXsOqmRZdFFVv7V6iVkh8,12602
|
|
18
20
|
pltr/commands/verify.py,sha256=n8LWhbfGieYa-a5_l3MxqkYbdpyVf8-i0FQIL__AaPA,6650
|
|
19
21
|
pltr/config/__init__.py,sha256=Y6gARy5lUHy-OJaOUxtfXoeQVNZV5QHLl6uKHQ8tpTk,41
|
|
22
|
+
pltr/config/aliases.py,sha256=ZmesZWMfa5riZlVe3fyC7EI3uIzxEGsDHz-8shOpIbM,6947
|
|
20
23
|
pltr/config/profiles.py,sha256=XMUIp6Ez5LNC6rGXZe2JLH7IKepXhARtuc8ASUA9FYA,3431
|
|
21
24
|
pltr/config/settings.py,sha256=bfIiosPqH_W73TOHS71DvgZdAHka4fJDopU1SvBRFuQ,2908
|
|
22
25
|
pltr/services/__init__.py,sha256=zQpgrqPdAkZI-nobi33mctU2-iGNgazzvjBVY8YRbSQ,101
|
|
@@ -25,14 +28,16 @@ pltr/services/base.py,sha256=R2G781FI-sXtjUyLd91bVnmLb4cYZI3G8U5ndR9NLA4,1593
|
|
|
25
28
|
pltr/services/dataset.py,sha256=W3zoh-9YIJ6HBsDijejVEngKvpudFoZinYtHDmAXCOc,2785
|
|
26
29
|
pltr/services/dataset_full.py,sha256=FyMiwOSyX1cUrYXaK0T_1iq5G_X0e5iZTibJHuEmMeE,9869
|
|
27
30
|
pltr/services/dataset_v2.py,sha256=_uhcVJ91w_Y07glceqHceccAwPWr6q1TWSIqcP1FU8I,4259
|
|
28
|
-
pltr/services/
|
|
31
|
+
pltr/services/folder.py,sha256=mWElyvn-wXPB5sv8Ik_dLeW5JM6jZg3g9KKBk6UcrlQ,5389
|
|
32
|
+
pltr/services/ontology.py,sha256=iW7qRK8ptlw-u4eAwLNC-mdzLoLZzh7SRqJyok2c3GU,14883
|
|
29
33
|
pltr/services/sql.py,sha256=19cscjlzN8WE1s8_ctiRcrOvMzCfmWRj49vjJ8Gs5Q4,11286
|
|
30
34
|
pltr/utils/__init__.py,sha256=DF7TigL1XbKVGM5VjgU8_8AGIszofkdO80oCzLGGnTE,38
|
|
31
|
-
pltr/utils/
|
|
35
|
+
pltr/utils/alias_resolver.py,sha256=DIF7P1UnUU8kqocJfIDEWjYq4s8_0KfqRZBbECeZEh8,1539
|
|
36
|
+
pltr/utils/completion.py,sha256=bjeqjleEfB2YcQFpcxvF0GoQ763F6KBbULSZC4FWY_g,4980
|
|
32
37
|
pltr/utils/formatting.py,sha256=Ptv7obiB7uWNf5YTiY4rUAKmabO5UGnwLocSfLplrZ0,18552
|
|
33
38
|
pltr/utils/progress.py,sha256=BKYbiLO61uhQbibabU7pxvvbAWMRLRmqk4pZldBQK_g,9053
|
|
34
|
-
pltr_cli-0.
|
|
35
|
-
pltr_cli-0.
|
|
36
|
-
pltr_cli-0.
|
|
37
|
-
pltr_cli-0.
|
|
38
|
-
pltr_cli-0.
|
|
39
|
+
pltr_cli-0.4.0.dist-info/METADATA,sha256=RDHEVijMZLcjzM8fBjaNm8DQsY0PIK8339CbDK64s-s,9096
|
|
40
|
+
pltr_cli-0.4.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
41
|
+
pltr_cli-0.4.0.dist-info/entry_points.txt,sha256=8tvEcW04kA_oAE2Dwwu-Og9efjl4ESJvs4AzlP2KBdQ,38
|
|
42
|
+
pltr_cli-0.4.0.dist-info/licenses/LICENSE,sha256=6VUFd_ytnOBD2O1tmkKrA-smigi9QEhYr_tge4h4z8Y,1070
|
|
43
|
+
pltr_cli-0.4.0.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|