pltr-cli 0.1.1__py3-none-any.whl → 0.2.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 +28 -0
- pltr/cli.py +19 -1
- pltr/commands/admin.py +530 -0
- pltr/commands/completion.py +383 -0
- pltr/commands/dataset.py +20 -3
- pltr/commands/ontology.py +508 -0
- pltr/commands/shell.py +126 -0
- pltr/commands/sql.py +358 -0
- pltr/commands/verify.py +2 -1
- pltr/services/__init__.py +4 -0
- pltr/services/admin.py +314 -0
- pltr/services/ontology.py +443 -0
- pltr/services/sql.py +340 -0
- pltr/utils/completion.py +170 -0
- pltr/utils/formatting.py +208 -0
- pltr/utils/progress.py +1 -1
- pltr_cli-0.2.0.dist-info/METADATA +280 -0
- pltr_cli-0.2.0.dist-info/RECORD +38 -0
- pltr_cli-0.1.1.dist-info/METADATA +0 -203
- pltr_cli-0.1.1.dist-info/RECORD +0 -28
- {pltr_cli-0.1.1.dist-info → pltr_cli-0.2.0.dist-info}/WHEEL +0 -0
- {pltr_cli-0.1.1.dist-info → pltr_cli-0.2.0.dist-info}/entry_points.txt +0 -0
- {pltr_cli-0.1.1.dist-info → pltr_cli-0.2.0.dist-info}/licenses/LICENSE +0 -0
|
@@ -0,0 +1,508 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Ontology commands for interacting with Foundry ontologies.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import json
|
|
6
|
+
import typer
|
|
7
|
+
from typing import Optional
|
|
8
|
+
from rich.console import Console
|
|
9
|
+
|
|
10
|
+
from ..services.ontology import (
|
|
11
|
+
OntologyService,
|
|
12
|
+
ObjectTypeService,
|
|
13
|
+
OntologyObjectService,
|
|
14
|
+
ActionService,
|
|
15
|
+
QueryService,
|
|
16
|
+
)
|
|
17
|
+
from ..utils.formatting import OutputFormatter
|
|
18
|
+
from ..utils.progress import SpinnerProgressTracker
|
|
19
|
+
from ..auth.base import ProfileNotFoundError, MissingCredentialsError
|
|
20
|
+
|
|
21
|
+
app = typer.Typer(help="Ontology operations")
|
|
22
|
+
console = Console()
|
|
23
|
+
formatter = OutputFormatter(console)
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
# Ontology management commands
|
|
27
|
+
@app.command("list")
|
|
28
|
+
def list_ontologies(
|
|
29
|
+
profile: Optional[str] = typer.Option(None, "--profile", "-p", help="Profile name"),
|
|
30
|
+
format: str = typer.Option(
|
|
31
|
+
"table", "--format", "-f", help="Output format (table, json, csv)"
|
|
32
|
+
),
|
|
33
|
+
output: Optional[str] = typer.Option(
|
|
34
|
+
None, "--output", "-o", help="Output file path"
|
|
35
|
+
),
|
|
36
|
+
page_size: Optional[int] = typer.Option(
|
|
37
|
+
None, "--page-size", help="Number of results per page"
|
|
38
|
+
),
|
|
39
|
+
):
|
|
40
|
+
"""List all available ontologies."""
|
|
41
|
+
try:
|
|
42
|
+
service = OntologyService(profile=profile)
|
|
43
|
+
|
|
44
|
+
with SpinnerProgressTracker().track_spinner("Fetching ontologies..."):
|
|
45
|
+
ontologies = service.list_ontologies(page_size=page_size)
|
|
46
|
+
|
|
47
|
+
formatter.format_table(
|
|
48
|
+
ontologies,
|
|
49
|
+
columns=["rid", "api_name", "display_name", "description"],
|
|
50
|
+
format=format,
|
|
51
|
+
output=output,
|
|
52
|
+
)
|
|
53
|
+
|
|
54
|
+
if output:
|
|
55
|
+
formatter.print_success(f"Ontologies saved to {output}")
|
|
56
|
+
|
|
57
|
+
except (ProfileNotFoundError, MissingCredentialsError) as e:
|
|
58
|
+
formatter.print_error(f"Authentication error: {e}")
|
|
59
|
+
raise typer.Exit(1)
|
|
60
|
+
except Exception as e:
|
|
61
|
+
formatter.print_error(f"Failed to list ontologies: {e}")
|
|
62
|
+
raise typer.Exit(1)
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
@app.command("get")
|
|
66
|
+
def get_ontology(
|
|
67
|
+
ontology_rid: str = typer.Argument(..., help="Ontology Resource Identifier"),
|
|
68
|
+
profile: Optional[str] = typer.Option(None, "--profile", "-p", help="Profile name"),
|
|
69
|
+
format: str = typer.Option(
|
|
70
|
+
"table", "--format", "-f", help="Output format (table, json, csv)"
|
|
71
|
+
),
|
|
72
|
+
output: Optional[str] = typer.Option(
|
|
73
|
+
None, "--output", "-o", help="Output file path"
|
|
74
|
+
),
|
|
75
|
+
):
|
|
76
|
+
"""Get details of a specific ontology."""
|
|
77
|
+
try:
|
|
78
|
+
service = OntologyService(profile=profile)
|
|
79
|
+
|
|
80
|
+
with SpinnerProgressTracker().track_spinner(
|
|
81
|
+
f"Fetching ontology {ontology_rid}..."
|
|
82
|
+
):
|
|
83
|
+
ontology = service.get_ontology(ontology_rid)
|
|
84
|
+
|
|
85
|
+
formatter.format_dict(ontology, format=format, output=output)
|
|
86
|
+
|
|
87
|
+
if output:
|
|
88
|
+
formatter.print_success(f"Ontology information saved to {output}")
|
|
89
|
+
|
|
90
|
+
except (ProfileNotFoundError, MissingCredentialsError) as e:
|
|
91
|
+
formatter.print_error(f"Authentication error: {e}")
|
|
92
|
+
raise typer.Exit(1)
|
|
93
|
+
except Exception as e:
|
|
94
|
+
formatter.print_error(f"Failed to get ontology: {e}")
|
|
95
|
+
raise typer.Exit(1)
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
# Object Type commands
|
|
99
|
+
@app.command("object-type-list")
|
|
100
|
+
def list_object_types(
|
|
101
|
+
ontology_rid: str = typer.Argument(..., help="Ontology Resource Identifier"),
|
|
102
|
+
profile: Optional[str] = typer.Option(None, "--profile", "-p", help="Profile name"),
|
|
103
|
+
format: str = typer.Option(
|
|
104
|
+
"table", "--format", "-f", help="Output format (table, json, csv)"
|
|
105
|
+
),
|
|
106
|
+
output: Optional[str] = typer.Option(
|
|
107
|
+
None, "--output", "-o", help="Output file path"
|
|
108
|
+
),
|
|
109
|
+
page_size: Optional[int] = typer.Option(
|
|
110
|
+
None, "--page-size", help="Number of results per page"
|
|
111
|
+
),
|
|
112
|
+
):
|
|
113
|
+
"""List object types in an ontology."""
|
|
114
|
+
try:
|
|
115
|
+
service = ObjectTypeService(profile=profile)
|
|
116
|
+
|
|
117
|
+
with SpinnerProgressTracker().track_spinner("Fetching object types..."):
|
|
118
|
+
object_types = service.list_object_types(ontology_rid, page_size=page_size)
|
|
119
|
+
|
|
120
|
+
formatter.format_table(
|
|
121
|
+
object_types,
|
|
122
|
+
columns=["api_name", "display_name", "description", "primary_key"],
|
|
123
|
+
format=format,
|
|
124
|
+
output=output,
|
|
125
|
+
)
|
|
126
|
+
|
|
127
|
+
if output:
|
|
128
|
+
formatter.print_success(f"Object types saved to {output}")
|
|
129
|
+
|
|
130
|
+
except (ProfileNotFoundError, MissingCredentialsError) as e:
|
|
131
|
+
formatter.print_error(f"Authentication error: {e}")
|
|
132
|
+
raise typer.Exit(1)
|
|
133
|
+
except Exception as e:
|
|
134
|
+
formatter.print_error(f"Failed to list object types: {e}")
|
|
135
|
+
raise typer.Exit(1)
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
@app.command("object-type-get")
|
|
139
|
+
def get_object_type(
|
|
140
|
+
ontology_rid: str = typer.Argument(..., help="Ontology Resource Identifier"),
|
|
141
|
+
object_type: str = typer.Argument(..., help="Object type API name"),
|
|
142
|
+
profile: Optional[str] = typer.Option(None, "--profile", "-p", help="Profile name"),
|
|
143
|
+
format: str = typer.Option(
|
|
144
|
+
"table", "--format", "-f", help="Output format (table, json, csv)"
|
|
145
|
+
),
|
|
146
|
+
output: Optional[str] = typer.Option(
|
|
147
|
+
None, "--output", "-o", help="Output file path"
|
|
148
|
+
),
|
|
149
|
+
):
|
|
150
|
+
"""Get details of a specific object type."""
|
|
151
|
+
try:
|
|
152
|
+
service = ObjectTypeService(profile=profile)
|
|
153
|
+
|
|
154
|
+
with SpinnerProgressTracker().track_spinner(
|
|
155
|
+
f"Fetching object type {object_type}..."
|
|
156
|
+
):
|
|
157
|
+
obj_type = service.get_object_type(ontology_rid, object_type)
|
|
158
|
+
|
|
159
|
+
formatter.format_dict(obj_type, format=format, output=output)
|
|
160
|
+
|
|
161
|
+
if output:
|
|
162
|
+
formatter.print_success(f"Object type information saved to {output}")
|
|
163
|
+
|
|
164
|
+
except (ProfileNotFoundError, MissingCredentialsError) as e:
|
|
165
|
+
formatter.print_error(f"Authentication error: {e}")
|
|
166
|
+
raise typer.Exit(1)
|
|
167
|
+
except Exception as e:
|
|
168
|
+
formatter.print_error(f"Failed to get object type: {e}")
|
|
169
|
+
raise typer.Exit(1)
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
# Object operations
|
|
173
|
+
@app.command("object-list")
|
|
174
|
+
def list_objects(
|
|
175
|
+
ontology_rid: str = typer.Argument(..., help="Ontology Resource Identifier"),
|
|
176
|
+
object_type: str = typer.Argument(..., help="Object type API name"),
|
|
177
|
+
profile: Optional[str] = typer.Option(None, "--profile", "-p", help="Profile name"),
|
|
178
|
+
format: str = typer.Option(
|
|
179
|
+
"table", "--format", "-f", help="Output format (table, json, csv)"
|
|
180
|
+
),
|
|
181
|
+
output: Optional[str] = typer.Option(
|
|
182
|
+
None, "--output", "-o", help="Output file path"
|
|
183
|
+
),
|
|
184
|
+
page_size: Optional[int] = typer.Option(
|
|
185
|
+
None, "--page-size", help="Number of results per page"
|
|
186
|
+
),
|
|
187
|
+
properties: Optional[str] = typer.Option(
|
|
188
|
+
None, "--properties", help="Comma-separated list of properties to include"
|
|
189
|
+
),
|
|
190
|
+
):
|
|
191
|
+
"""List objects of a specific type."""
|
|
192
|
+
try:
|
|
193
|
+
service = OntologyObjectService(profile=profile)
|
|
194
|
+
|
|
195
|
+
prop_list = properties.split(",") if properties else None
|
|
196
|
+
|
|
197
|
+
with SpinnerProgressTracker().track_spinner(
|
|
198
|
+
f"Fetching {object_type} objects..."
|
|
199
|
+
):
|
|
200
|
+
objects = service.list_objects(
|
|
201
|
+
ontology_rid, object_type, page_size=page_size, properties=prop_list
|
|
202
|
+
)
|
|
203
|
+
|
|
204
|
+
if format == "table" and objects:
|
|
205
|
+
# Use first object's keys as columns
|
|
206
|
+
columns = list(objects[0].keys()) if objects else []
|
|
207
|
+
formatter.format_table(
|
|
208
|
+
objects, columns=columns, format=format, output=output
|
|
209
|
+
)
|
|
210
|
+
else:
|
|
211
|
+
formatter.format_list(objects, format=format, output=output)
|
|
212
|
+
|
|
213
|
+
if output:
|
|
214
|
+
formatter.print_success(f"Objects saved to {output}")
|
|
215
|
+
|
|
216
|
+
except (ProfileNotFoundError, MissingCredentialsError) as e:
|
|
217
|
+
formatter.print_error(f"Authentication error: {e}")
|
|
218
|
+
raise typer.Exit(1)
|
|
219
|
+
except Exception as e:
|
|
220
|
+
formatter.print_error(f"Failed to list objects: {e}")
|
|
221
|
+
raise typer.Exit(1)
|
|
222
|
+
|
|
223
|
+
|
|
224
|
+
@app.command("object-get")
|
|
225
|
+
def get_object(
|
|
226
|
+
ontology_rid: str = typer.Argument(..., help="Ontology Resource Identifier"),
|
|
227
|
+
object_type: str = typer.Argument(..., help="Object type API name"),
|
|
228
|
+
primary_key: str = typer.Argument(..., help="Object primary key"),
|
|
229
|
+
profile: Optional[str] = typer.Option(None, "--profile", "-p", help="Profile name"),
|
|
230
|
+
format: str = typer.Option(
|
|
231
|
+
"table", "--format", "-f", help="Output format (table, json, csv)"
|
|
232
|
+
),
|
|
233
|
+
output: Optional[str] = typer.Option(
|
|
234
|
+
None, "--output", "-o", help="Output file path"
|
|
235
|
+
),
|
|
236
|
+
properties: Optional[str] = typer.Option(
|
|
237
|
+
None, "--properties", help="Comma-separated list of properties to include"
|
|
238
|
+
),
|
|
239
|
+
):
|
|
240
|
+
"""Get a specific object by primary key."""
|
|
241
|
+
try:
|
|
242
|
+
service = OntologyObjectService(profile=profile)
|
|
243
|
+
|
|
244
|
+
prop_list = properties.split(",") if properties else None
|
|
245
|
+
|
|
246
|
+
with SpinnerProgressTracker().track_spinner(
|
|
247
|
+
f"Fetching object {primary_key}..."
|
|
248
|
+
):
|
|
249
|
+
obj = service.get_object(
|
|
250
|
+
ontology_rid, object_type, primary_key, properties=prop_list
|
|
251
|
+
)
|
|
252
|
+
|
|
253
|
+
formatter.format_dict(obj, format=format, output=output)
|
|
254
|
+
|
|
255
|
+
if output:
|
|
256
|
+
formatter.print_success(f"Object information saved to {output}")
|
|
257
|
+
|
|
258
|
+
except (ProfileNotFoundError, MissingCredentialsError) as e:
|
|
259
|
+
formatter.print_error(f"Authentication error: {e}")
|
|
260
|
+
raise typer.Exit(1)
|
|
261
|
+
except Exception as e:
|
|
262
|
+
formatter.print_error(f"Failed to get object: {e}")
|
|
263
|
+
raise typer.Exit(1)
|
|
264
|
+
|
|
265
|
+
|
|
266
|
+
@app.command("object-aggregate")
|
|
267
|
+
def aggregate_objects(
|
|
268
|
+
ontology_rid: str = typer.Argument(..., help="Ontology Resource Identifier"),
|
|
269
|
+
object_type: str = typer.Argument(..., help="Object type API name"),
|
|
270
|
+
aggregations: str = typer.Argument(..., help="JSON string of aggregation specs"),
|
|
271
|
+
profile: Optional[str] = typer.Option(None, "--profile", "-p", help="Profile name"),
|
|
272
|
+
format: str = typer.Option(
|
|
273
|
+
"table", "--format", "-f", help="Output format (table, json, csv)"
|
|
274
|
+
),
|
|
275
|
+
output: Optional[str] = typer.Option(
|
|
276
|
+
None, "--output", "-o", help="Output file path"
|
|
277
|
+
),
|
|
278
|
+
group_by: Optional[str] = typer.Option(
|
|
279
|
+
None, "--group-by", help="Comma-separated list of fields to group by"
|
|
280
|
+
),
|
|
281
|
+
filter: Optional[str] = typer.Option(
|
|
282
|
+
None, "--filter", help="JSON string of filter criteria"
|
|
283
|
+
),
|
|
284
|
+
):
|
|
285
|
+
"""Aggregate objects with specified functions."""
|
|
286
|
+
try:
|
|
287
|
+
service = OntologyObjectService(profile=profile)
|
|
288
|
+
|
|
289
|
+
# Parse JSON inputs
|
|
290
|
+
agg_list = json.loads(aggregations)
|
|
291
|
+
group_list = group_by.split(",") if group_by else None
|
|
292
|
+
filter_dict = json.loads(filter) if filter else None
|
|
293
|
+
|
|
294
|
+
with SpinnerProgressTracker().track_spinner("Aggregating objects..."):
|
|
295
|
+
result = service.aggregate_objects(
|
|
296
|
+
ontology_rid,
|
|
297
|
+
object_type,
|
|
298
|
+
agg_list,
|
|
299
|
+
group_by=group_list,
|
|
300
|
+
filter=filter_dict,
|
|
301
|
+
)
|
|
302
|
+
|
|
303
|
+
formatter.format_dict(result, format=format, output=output)
|
|
304
|
+
|
|
305
|
+
if output:
|
|
306
|
+
formatter.print_success(f"Aggregation results saved to {output}")
|
|
307
|
+
|
|
308
|
+
except json.JSONDecodeError as e:
|
|
309
|
+
formatter.print_error(f"Invalid JSON: {e}")
|
|
310
|
+
raise typer.Exit(1)
|
|
311
|
+
except (ProfileNotFoundError, MissingCredentialsError) as e:
|
|
312
|
+
formatter.print_error(f"Authentication error: {e}")
|
|
313
|
+
raise typer.Exit(1)
|
|
314
|
+
except Exception as e:
|
|
315
|
+
formatter.print_error(f"Failed to aggregate objects: {e}")
|
|
316
|
+
raise typer.Exit(1)
|
|
317
|
+
|
|
318
|
+
|
|
319
|
+
@app.command("object-linked")
|
|
320
|
+
def list_linked_objects(
|
|
321
|
+
ontology_rid: str = typer.Argument(..., help="Ontology Resource Identifier"),
|
|
322
|
+
object_type: str = typer.Argument(..., help="Object type API name"),
|
|
323
|
+
primary_key: str = typer.Argument(..., help="Object primary key"),
|
|
324
|
+
link_type: str = typer.Argument(..., help="Link type API name"),
|
|
325
|
+
profile: Optional[str] = typer.Option(None, "--profile", "-p", help="Profile name"),
|
|
326
|
+
format: str = typer.Option(
|
|
327
|
+
"table", "--format", "-f", help="Output format (table, json, csv)"
|
|
328
|
+
),
|
|
329
|
+
output: Optional[str] = typer.Option(
|
|
330
|
+
None, "--output", "-o", help="Output file path"
|
|
331
|
+
),
|
|
332
|
+
page_size: Optional[int] = typer.Option(
|
|
333
|
+
None, "--page-size", help="Number of results per page"
|
|
334
|
+
),
|
|
335
|
+
properties: Optional[str] = typer.Option(
|
|
336
|
+
None, "--properties", help="Comma-separated list of properties to include"
|
|
337
|
+
),
|
|
338
|
+
):
|
|
339
|
+
"""List objects linked to a specific object."""
|
|
340
|
+
try:
|
|
341
|
+
service = OntologyObjectService(profile=profile)
|
|
342
|
+
|
|
343
|
+
prop_list = properties.split(",") if properties else None
|
|
344
|
+
|
|
345
|
+
with SpinnerProgressTracker().track_spinner("Fetching linked objects..."):
|
|
346
|
+
objects = service.list_linked_objects(
|
|
347
|
+
ontology_rid,
|
|
348
|
+
object_type,
|
|
349
|
+
primary_key,
|
|
350
|
+
link_type,
|
|
351
|
+
page_size=page_size,
|
|
352
|
+
properties=prop_list,
|
|
353
|
+
)
|
|
354
|
+
|
|
355
|
+
if format == "table" and objects:
|
|
356
|
+
# Use first object's keys as columns
|
|
357
|
+
columns = list(objects[0].keys()) if objects else []
|
|
358
|
+
formatter.format_table(
|
|
359
|
+
objects, columns=columns, format=format, output=output
|
|
360
|
+
)
|
|
361
|
+
else:
|
|
362
|
+
formatter.format_list(objects, format=format, output=output)
|
|
363
|
+
|
|
364
|
+
if output:
|
|
365
|
+
formatter.print_success(f"Linked objects saved to {output}")
|
|
366
|
+
|
|
367
|
+
except (ProfileNotFoundError, MissingCredentialsError) as e:
|
|
368
|
+
formatter.print_error(f"Authentication error: {e}")
|
|
369
|
+
raise typer.Exit(1)
|
|
370
|
+
except Exception as e:
|
|
371
|
+
formatter.print_error(f"Failed to list linked objects: {e}")
|
|
372
|
+
raise typer.Exit(1)
|
|
373
|
+
|
|
374
|
+
|
|
375
|
+
# Action commands
|
|
376
|
+
@app.command("action-apply")
|
|
377
|
+
def apply_action(
|
|
378
|
+
ontology_rid: str = typer.Argument(..., help="Ontology Resource Identifier"),
|
|
379
|
+
action_type: str = typer.Argument(..., help="Action type API name"),
|
|
380
|
+
parameters: str = typer.Argument(..., help="JSON string of action parameters"),
|
|
381
|
+
profile: Optional[str] = typer.Option(None, "--profile", "-p", help="Profile name"),
|
|
382
|
+
format: str = typer.Option(
|
|
383
|
+
"table", "--format", "-f", help="Output format (table, json, csv)"
|
|
384
|
+
),
|
|
385
|
+
output: Optional[str] = typer.Option(
|
|
386
|
+
None, "--output", "-o", help="Output file path"
|
|
387
|
+
),
|
|
388
|
+
):
|
|
389
|
+
"""Apply an action with given parameters."""
|
|
390
|
+
try:
|
|
391
|
+
service = ActionService(profile=profile)
|
|
392
|
+
|
|
393
|
+
# Parse JSON parameters
|
|
394
|
+
params = json.loads(parameters)
|
|
395
|
+
|
|
396
|
+
with SpinnerProgressTracker().track_spinner(
|
|
397
|
+
f"Applying action {action_type}..."
|
|
398
|
+
):
|
|
399
|
+
result = service.apply_action(ontology_rid, action_type, params)
|
|
400
|
+
|
|
401
|
+
formatter.format_dict(result, format=format, output=output)
|
|
402
|
+
|
|
403
|
+
if output:
|
|
404
|
+
formatter.print_success(f"Action result saved to {output}")
|
|
405
|
+
|
|
406
|
+
except json.JSONDecodeError as e:
|
|
407
|
+
formatter.print_error(f"Invalid JSON: {e}")
|
|
408
|
+
raise typer.Exit(1)
|
|
409
|
+
except (ProfileNotFoundError, MissingCredentialsError) as e:
|
|
410
|
+
formatter.print_error(f"Authentication error: {e}")
|
|
411
|
+
raise typer.Exit(1)
|
|
412
|
+
except Exception as e:
|
|
413
|
+
formatter.print_error(f"Failed to apply action: {e}")
|
|
414
|
+
raise typer.Exit(1)
|
|
415
|
+
|
|
416
|
+
|
|
417
|
+
@app.command("action-validate")
|
|
418
|
+
def validate_action(
|
|
419
|
+
ontology_rid: str = typer.Argument(..., help="Ontology Resource Identifier"),
|
|
420
|
+
action_type: str = typer.Argument(..., help="Action type API name"),
|
|
421
|
+
parameters: str = typer.Argument(..., help="JSON string of action parameters"),
|
|
422
|
+
profile: Optional[str] = typer.Option(None, "--profile", "-p", help="Profile name"),
|
|
423
|
+
format: str = typer.Option(
|
|
424
|
+
"table", "--format", "-f", help="Output format (table, json, csv)"
|
|
425
|
+
),
|
|
426
|
+
output: Optional[str] = typer.Option(
|
|
427
|
+
None, "--output", "-o", help="Output file path"
|
|
428
|
+
),
|
|
429
|
+
):
|
|
430
|
+
"""Validate action parameters without executing."""
|
|
431
|
+
try:
|
|
432
|
+
service = ActionService(profile=profile)
|
|
433
|
+
|
|
434
|
+
# Parse JSON parameters
|
|
435
|
+
params = json.loads(parameters)
|
|
436
|
+
|
|
437
|
+
with SpinnerProgressTracker().track_spinner(
|
|
438
|
+
f"Validating action {action_type}..."
|
|
439
|
+
):
|
|
440
|
+
result = service.validate_action(ontology_rid, action_type, params)
|
|
441
|
+
|
|
442
|
+
formatter.format_dict(result, format=format, output=output)
|
|
443
|
+
|
|
444
|
+
if result.get("valid"):
|
|
445
|
+
formatter.print_success("Action parameters are valid")
|
|
446
|
+
else:
|
|
447
|
+
formatter.print_error("Action parameters are invalid")
|
|
448
|
+
|
|
449
|
+
if output:
|
|
450
|
+
formatter.print_success(f"Validation result saved to {output}")
|
|
451
|
+
|
|
452
|
+
except json.JSONDecodeError as e:
|
|
453
|
+
formatter.print_error(f"Invalid JSON: {e}")
|
|
454
|
+
raise typer.Exit(1)
|
|
455
|
+
except (ProfileNotFoundError, MissingCredentialsError) as e:
|
|
456
|
+
formatter.print_error(f"Authentication error: {e}")
|
|
457
|
+
raise typer.Exit(1)
|
|
458
|
+
except Exception as e:
|
|
459
|
+
formatter.print_error(f"Failed to validate action: {e}")
|
|
460
|
+
raise typer.Exit(1)
|
|
461
|
+
|
|
462
|
+
|
|
463
|
+
# Query commands
|
|
464
|
+
@app.command("query-execute")
|
|
465
|
+
def execute_query(
|
|
466
|
+
ontology_rid: str = typer.Argument(..., help="Ontology Resource Identifier"),
|
|
467
|
+
query_name: str = typer.Argument(..., help="Query API name"),
|
|
468
|
+
profile: Optional[str] = typer.Option(None, "--profile", "-p", help="Profile name"),
|
|
469
|
+
format: str = typer.Option(
|
|
470
|
+
"table", "--format", "-f", help="Output format (table, json, csv)"
|
|
471
|
+
),
|
|
472
|
+
output: Optional[str] = typer.Option(
|
|
473
|
+
None, "--output", "-o", help="Output file path"
|
|
474
|
+
),
|
|
475
|
+
parameters: Optional[str] = typer.Option(
|
|
476
|
+
None, "--parameters", help="JSON string of query parameters"
|
|
477
|
+
),
|
|
478
|
+
):
|
|
479
|
+
"""Execute a predefined query."""
|
|
480
|
+
try:
|
|
481
|
+
service = QueryService(profile=profile)
|
|
482
|
+
|
|
483
|
+
# Parse JSON parameters if provided
|
|
484
|
+
params = json.loads(parameters) if parameters else None
|
|
485
|
+
|
|
486
|
+
with SpinnerProgressTracker().track_spinner(f"Executing query {query_name}..."):
|
|
487
|
+
result = service.execute_query(ontology_rid, query_name, parameters=params)
|
|
488
|
+
|
|
489
|
+
# Handle different result formats
|
|
490
|
+
if "rows" in result:
|
|
491
|
+
formatter.format_list(result["rows"], format=format, output=output)
|
|
492
|
+
elif "objects" in result:
|
|
493
|
+
formatter.format_list(result["objects"], format=format, output=output)
|
|
494
|
+
else:
|
|
495
|
+
formatter.format_dict(result, format=format, output=output)
|
|
496
|
+
|
|
497
|
+
if output:
|
|
498
|
+
formatter.print_success(f"Query results saved to {output}")
|
|
499
|
+
|
|
500
|
+
except json.JSONDecodeError as e:
|
|
501
|
+
formatter.print_error(f"Invalid JSON: {e}")
|
|
502
|
+
raise typer.Exit(1)
|
|
503
|
+
except (ProfileNotFoundError, MissingCredentialsError) as e:
|
|
504
|
+
formatter.print_error(f"Authentication error: {e}")
|
|
505
|
+
raise typer.Exit(1)
|
|
506
|
+
except Exception as e:
|
|
507
|
+
formatter.print_error(f"Failed to execute query: {e}")
|
|
508
|
+
raise typer.Exit(1)
|
pltr/commands/shell.py
ADDED
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Interactive shell (REPL) command for the pltr CLI.
|
|
3
|
+
Provides an interactive mode with tab completion and command history.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
import os
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
from typing import Optional
|
|
9
|
+
|
|
10
|
+
import typer
|
|
11
|
+
from click_repl import repl # type: ignore
|
|
12
|
+
from prompt_toolkit.history import FileHistory
|
|
13
|
+
from rich.console import Console
|
|
14
|
+
|
|
15
|
+
from ..config.profiles import ProfileManager
|
|
16
|
+
|
|
17
|
+
shell_app = typer.Typer(
|
|
18
|
+
name="shell",
|
|
19
|
+
help="Start an interactive shell session with tab completion and history",
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def get_history_file() -> Path:
|
|
24
|
+
"""Get the path to the history file for the REPL."""
|
|
25
|
+
config_dir = Path.home() / ".config" / "pltr"
|
|
26
|
+
config_dir.mkdir(parents=True, exist_ok=True)
|
|
27
|
+
return config_dir / "repl_history"
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def get_prompt() -> str:
|
|
31
|
+
"""Get the prompt string for the REPL."""
|
|
32
|
+
try:
|
|
33
|
+
profile_manager = ProfileManager()
|
|
34
|
+
current_profile = profile_manager.get_active_profile()
|
|
35
|
+
if current_profile:
|
|
36
|
+
return f"pltr ({current_profile})> "
|
|
37
|
+
else:
|
|
38
|
+
return "pltr> "
|
|
39
|
+
except Exception:
|
|
40
|
+
return "pltr> "
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
@shell_app.command()
|
|
44
|
+
def start(
|
|
45
|
+
profile: Optional[str] = typer.Option(
|
|
46
|
+
None, "--profile", help="Auth profile to use for the session"
|
|
47
|
+
),
|
|
48
|
+
) -> None:
|
|
49
|
+
"""
|
|
50
|
+
Start an interactive shell session for pltr CLI.
|
|
51
|
+
|
|
52
|
+
Features:
|
|
53
|
+
- Tab completion for all commands
|
|
54
|
+
- Command history (persistent across sessions)
|
|
55
|
+
- Current profile displayed in prompt
|
|
56
|
+
- All pltr commands available without the 'pltr' prefix
|
|
57
|
+
|
|
58
|
+
Examples:
|
|
59
|
+
# Start interactive shell
|
|
60
|
+
$ pltr shell
|
|
61
|
+
|
|
62
|
+
# In the shell, run commands without 'pltr' prefix:
|
|
63
|
+
pltr> dataset get ri.foundry.main.dataset.123
|
|
64
|
+
pltr> ontology list
|
|
65
|
+
pltr> sql execute "SELECT * FROM dataset LIMIT 10"
|
|
66
|
+
|
|
67
|
+
# Exit the shell:
|
|
68
|
+
pltr> exit
|
|
69
|
+
"""
|
|
70
|
+
console = Console()
|
|
71
|
+
|
|
72
|
+
# Set profile if specified
|
|
73
|
+
if profile:
|
|
74
|
+
os.environ["PLTR_PROFILE"] = profile
|
|
75
|
+
console.print(f"[green]Using profile: {profile}[/green]")
|
|
76
|
+
|
|
77
|
+
# Welcome message
|
|
78
|
+
console.print("\n[bold cyan]Welcome to pltr interactive shell![/bold cyan]")
|
|
79
|
+
console.print("Type 'help' for available commands, 'exit' to quit.\n")
|
|
80
|
+
|
|
81
|
+
# Import here to avoid circular dependency
|
|
82
|
+
from ..cli import app as main_app
|
|
83
|
+
|
|
84
|
+
# Convert Typer app to Click object and create context
|
|
85
|
+
# This is the correct way to integrate click-repl with Typer
|
|
86
|
+
from typer.main import get_command
|
|
87
|
+
|
|
88
|
+
click_app = get_command(main_app)
|
|
89
|
+
ctx = click_app.make_context("pltr", [])
|
|
90
|
+
|
|
91
|
+
# Start the REPL with the Click context
|
|
92
|
+
repl(
|
|
93
|
+
ctx,
|
|
94
|
+
prompt_kwargs={
|
|
95
|
+
"message": get_prompt,
|
|
96
|
+
"history": FileHistory(str(get_history_file())),
|
|
97
|
+
"complete_while_typing": True,
|
|
98
|
+
"enable_history_search": True,
|
|
99
|
+
},
|
|
100
|
+
)
|
|
101
|
+
|
|
102
|
+
console.print("\n[cyan]Goodbye![/cyan]")
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
# Make 'start' the default command when just running 'pltr shell'
|
|
106
|
+
@shell_app.callback(invoke_without_command=True)
|
|
107
|
+
def shell_callback(
|
|
108
|
+
ctx: typer.Context,
|
|
109
|
+
profile: Optional[str] = typer.Option(
|
|
110
|
+
None, "--profile", help="Auth profile to use for the session"
|
|
111
|
+
),
|
|
112
|
+
) -> None:
|
|
113
|
+
"""Interactive shell mode with tab completion and command history."""
|
|
114
|
+
if ctx.invoked_subcommand is None:
|
|
115
|
+
start(profile=profile)
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
# Alternative command name for convenience
|
|
119
|
+
@shell_app.command("interactive", hidden=True)
|
|
120
|
+
def interactive_alias(
|
|
121
|
+
profile: Optional[str] = typer.Option(
|
|
122
|
+
None, "--profile", help="Auth profile to use for the session"
|
|
123
|
+
),
|
|
124
|
+
) -> None:
|
|
125
|
+
"""Alias for 'start' command."""
|
|
126
|
+
start(profile=profile)
|