pltr-cli 0.1.2__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.2.dist-info/METADATA +0 -203
- pltr_cli-0.1.2.dist-info/RECORD +0 -28
- {pltr_cli-0.1.2.dist-info → pltr_cli-0.2.0.dist-info}/WHEEL +0 -0
- {pltr_cli-0.1.2.dist-info → pltr_cli-0.2.0.dist-info}/entry_points.txt +0 -0
- {pltr_cli-0.1.2.dist-info → pltr_cli-0.2.0.dist-info}/licenses/LICENSE +0 -0
pltr/commands/sql.py
ADDED
|
@@ -0,0 +1,358 @@
|
|
|
1
|
+
"""
|
|
2
|
+
SQL commands for the pltr CLI.
|
|
3
|
+
Provides commands for executing SQL queries against Foundry datasets.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
from typing import Optional
|
|
8
|
+
|
|
9
|
+
import typer
|
|
10
|
+
from rich.console import Console
|
|
11
|
+
|
|
12
|
+
from ..services.sql import SqlService
|
|
13
|
+
from ..utils.formatting import OutputFormatter
|
|
14
|
+
from ..utils.progress import SpinnerProgressTracker
|
|
15
|
+
from ..utils.completion import (
|
|
16
|
+
complete_profile,
|
|
17
|
+
complete_output_format,
|
|
18
|
+
complete_sql_query,
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
app = typer.Typer(name="sql", help="Execute SQL queries against Foundry datasets")
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
@app.command("execute")
|
|
25
|
+
def execute_query(
|
|
26
|
+
query: str = typer.Argument(
|
|
27
|
+
..., help="SQL query to execute", autocompletion=complete_sql_query
|
|
28
|
+
),
|
|
29
|
+
profile: Optional[str] = typer.Option(
|
|
30
|
+
None, "--profile", help="Auth profile to use", autocompletion=complete_profile
|
|
31
|
+
),
|
|
32
|
+
output_format: str = typer.Option(
|
|
33
|
+
"table",
|
|
34
|
+
"--format",
|
|
35
|
+
help="Output format (table, json, csv)",
|
|
36
|
+
autocompletion=complete_output_format,
|
|
37
|
+
),
|
|
38
|
+
output_file: Optional[Path] = typer.Option(
|
|
39
|
+
None, "--output", help="Save results to file"
|
|
40
|
+
),
|
|
41
|
+
timeout: int = typer.Option(300, "--timeout", help="Query timeout in seconds"),
|
|
42
|
+
fallback_branches: Optional[str] = typer.Option(
|
|
43
|
+
None, "--fallback-branches", help="Comma-separated list of fallback branch IDs"
|
|
44
|
+
),
|
|
45
|
+
) -> None:
|
|
46
|
+
"""Execute a SQL query and display results."""
|
|
47
|
+
console = Console()
|
|
48
|
+
formatter = OutputFormatter()
|
|
49
|
+
|
|
50
|
+
try:
|
|
51
|
+
# Parse fallback branches if provided
|
|
52
|
+
fallback_branch_ids = (
|
|
53
|
+
fallback_branches.split(",") if fallback_branches else None
|
|
54
|
+
)
|
|
55
|
+
|
|
56
|
+
# Create service and execute query
|
|
57
|
+
service = SqlService(profile=profile)
|
|
58
|
+
|
|
59
|
+
with SpinnerProgressTracker().track_spinner("Executing SQL query..."):
|
|
60
|
+
result = service.execute_query(
|
|
61
|
+
query=query,
|
|
62
|
+
fallback_branch_ids=fallback_branch_ids,
|
|
63
|
+
timeout=timeout,
|
|
64
|
+
format="table" if output_format in ["table", "csv"] else "json",
|
|
65
|
+
)
|
|
66
|
+
|
|
67
|
+
# Extract results
|
|
68
|
+
query_results = result.get("results", {})
|
|
69
|
+
|
|
70
|
+
# Display results
|
|
71
|
+
if output_file:
|
|
72
|
+
formatter.save_to_file(query_results, output_file, output_format)
|
|
73
|
+
console.print(f"[green]Results saved to {output_file}[/green]")
|
|
74
|
+
else:
|
|
75
|
+
formatter.display(query_results, output_format)
|
|
76
|
+
|
|
77
|
+
# Show query metadata
|
|
78
|
+
if "query_id" in result:
|
|
79
|
+
console.print(f"\n[dim]Query ID: {result['query_id']}[/dim]")
|
|
80
|
+
|
|
81
|
+
except Exception as e:
|
|
82
|
+
formatter.print_error(f"Failed to execute query: {e}")
|
|
83
|
+
raise typer.Exit(1)
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
@app.command("submit")
|
|
87
|
+
def submit_query(
|
|
88
|
+
query: str = typer.Argument(..., help="SQL query to submit"),
|
|
89
|
+
profile: Optional[str] = typer.Option(
|
|
90
|
+
None, "--profile", help="Auth profile to use"
|
|
91
|
+
),
|
|
92
|
+
fallback_branches: Optional[str] = typer.Option(
|
|
93
|
+
None, "--fallback-branches", help="Comma-separated list of fallback branch IDs"
|
|
94
|
+
),
|
|
95
|
+
) -> None:
|
|
96
|
+
"""Submit a SQL query without waiting for completion."""
|
|
97
|
+
console = Console()
|
|
98
|
+
formatter = OutputFormatter()
|
|
99
|
+
|
|
100
|
+
try:
|
|
101
|
+
# Parse fallback branches if provided
|
|
102
|
+
fallback_branch_ids = (
|
|
103
|
+
fallback_branches.split(",") if fallback_branches else None
|
|
104
|
+
)
|
|
105
|
+
|
|
106
|
+
# Create service and submit query
|
|
107
|
+
service = SqlService(profile=profile)
|
|
108
|
+
|
|
109
|
+
with SpinnerProgressTracker().track_spinner("Submitting SQL query..."):
|
|
110
|
+
result = service.submit_query(
|
|
111
|
+
query=query, fallback_branch_ids=fallback_branch_ids
|
|
112
|
+
)
|
|
113
|
+
|
|
114
|
+
console.print("[green]Query submitted successfully[/green]")
|
|
115
|
+
console.print(f"Query ID: [bold]{result.get('query_id', 'N/A')}[/bold]")
|
|
116
|
+
console.print(f"Status: [yellow]{result.get('status', 'unknown')}[/yellow]")
|
|
117
|
+
|
|
118
|
+
if result.get("status") == "succeeded":
|
|
119
|
+
console.print("[green]Query completed immediately[/green]")
|
|
120
|
+
elif result.get("status") == "running":
|
|
121
|
+
console.print(
|
|
122
|
+
"Use [bold]pltr sql status <query-id>[/bold] to check progress"
|
|
123
|
+
)
|
|
124
|
+
console.print(
|
|
125
|
+
"Use [bold]pltr sql results <query-id>[/bold] to get results when completed"
|
|
126
|
+
)
|
|
127
|
+
|
|
128
|
+
except Exception as e:
|
|
129
|
+
formatter.print_error(f"Failed to submit query: {e}")
|
|
130
|
+
raise typer.Exit(1)
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
@app.command("status")
|
|
134
|
+
def get_query_status(
|
|
135
|
+
query_id: str = typer.Argument(..., help="Query ID to check"),
|
|
136
|
+
profile: Optional[str] = typer.Option(
|
|
137
|
+
None, "--profile", help="Auth profile to use"
|
|
138
|
+
),
|
|
139
|
+
) -> None:
|
|
140
|
+
"""Get the status of a submitted query."""
|
|
141
|
+
console = Console()
|
|
142
|
+
formatter = OutputFormatter()
|
|
143
|
+
|
|
144
|
+
try:
|
|
145
|
+
service = SqlService(profile=profile)
|
|
146
|
+
|
|
147
|
+
with SpinnerProgressTracker().track_spinner("Checking query status..."):
|
|
148
|
+
result = service.get_query_status(query_id)
|
|
149
|
+
|
|
150
|
+
console.print(f"Query ID: [bold]{query_id}[/bold]")
|
|
151
|
+
|
|
152
|
+
status = result.get("status", "unknown")
|
|
153
|
+
if status == "running":
|
|
154
|
+
console.print(f"Status: [yellow]{status}[/yellow]")
|
|
155
|
+
console.print("Query is still executing...")
|
|
156
|
+
elif status == "succeeded":
|
|
157
|
+
console.print(f"Status: [green]{status}[/green]")
|
|
158
|
+
console.print("Use [bold]pltr sql results <query-id>[/bold] to get results")
|
|
159
|
+
elif status == "failed":
|
|
160
|
+
console.print(f"Status: [red]{status}[/red]")
|
|
161
|
+
error_msg = result.get("error_message", "Unknown error")
|
|
162
|
+
console.print(f"Error: {error_msg}")
|
|
163
|
+
elif status == "canceled":
|
|
164
|
+
console.print(f"Status: [red]{status}[/red]")
|
|
165
|
+
console.print("Query was canceled")
|
|
166
|
+
else:
|
|
167
|
+
console.print(f"Status: [dim]{status}[/dim]")
|
|
168
|
+
|
|
169
|
+
except Exception as e:
|
|
170
|
+
formatter.print_error(f"Failed to get query status: {e}")
|
|
171
|
+
raise typer.Exit(1)
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
@app.command("results")
|
|
175
|
+
def get_query_results(
|
|
176
|
+
query_id: str = typer.Argument(..., help="Query ID to get results for"),
|
|
177
|
+
profile: Optional[str] = typer.Option(
|
|
178
|
+
None, "--profile", help="Auth profile to use"
|
|
179
|
+
),
|
|
180
|
+
output_format: str = typer.Option(
|
|
181
|
+
"table", "--format", help="Output format (table, json, csv)"
|
|
182
|
+
),
|
|
183
|
+
output_file: Optional[Path] = typer.Option(
|
|
184
|
+
None, "--output", help="Save results to file"
|
|
185
|
+
),
|
|
186
|
+
) -> None:
|
|
187
|
+
"""Get the results of a completed query."""
|
|
188
|
+
console = Console()
|
|
189
|
+
formatter = OutputFormatter()
|
|
190
|
+
|
|
191
|
+
try:
|
|
192
|
+
service = SqlService(profile=profile)
|
|
193
|
+
|
|
194
|
+
with SpinnerProgressTracker().track_spinner("Retrieving query results..."):
|
|
195
|
+
result = service.get_query_results(
|
|
196
|
+
query_id,
|
|
197
|
+
format="table" if output_format in ["table", "csv"] else "json",
|
|
198
|
+
)
|
|
199
|
+
|
|
200
|
+
# Display or save results
|
|
201
|
+
if output_file:
|
|
202
|
+
formatter.save_to_file(result, output_file, output_format)
|
|
203
|
+
console.print(f"[green]Results saved to {output_file}[/green]")
|
|
204
|
+
else:
|
|
205
|
+
formatter.display(result, output_format)
|
|
206
|
+
|
|
207
|
+
console.print(f"\n[dim]Query ID: {query_id}[/dim]")
|
|
208
|
+
|
|
209
|
+
except Exception as e:
|
|
210
|
+
formatter.print_error(f"Failed to get query results: {e}")
|
|
211
|
+
raise typer.Exit(1)
|
|
212
|
+
|
|
213
|
+
|
|
214
|
+
@app.command("cancel")
|
|
215
|
+
def cancel_query(
|
|
216
|
+
query_id: str = typer.Argument(..., help="Query ID to cancel"),
|
|
217
|
+
profile: Optional[str] = typer.Option(
|
|
218
|
+
None, "--profile", help="Auth profile to use"
|
|
219
|
+
),
|
|
220
|
+
) -> None:
|
|
221
|
+
"""Cancel a running query."""
|
|
222
|
+
console = Console()
|
|
223
|
+
formatter = OutputFormatter()
|
|
224
|
+
|
|
225
|
+
try:
|
|
226
|
+
service = SqlService(profile=profile)
|
|
227
|
+
|
|
228
|
+
with SpinnerProgressTracker().track_spinner("Canceling query..."):
|
|
229
|
+
result = service.cancel_query(query_id)
|
|
230
|
+
|
|
231
|
+
console.print(f"Query ID: [bold]{query_id}[/bold]")
|
|
232
|
+
|
|
233
|
+
status = result.get("status", "unknown")
|
|
234
|
+
if status == "canceled":
|
|
235
|
+
console.print(f"Status: [red]{status}[/red]")
|
|
236
|
+
console.print("Query has been canceled successfully")
|
|
237
|
+
else:
|
|
238
|
+
console.print(f"Status: [yellow]{status}[/yellow]")
|
|
239
|
+
console.print("Query may have already completed or was not running")
|
|
240
|
+
|
|
241
|
+
except Exception as e:
|
|
242
|
+
formatter.print_error(f"Failed to cancel query: {e}")
|
|
243
|
+
raise typer.Exit(1)
|
|
244
|
+
|
|
245
|
+
|
|
246
|
+
@app.command("export")
|
|
247
|
+
def export_query_results(
|
|
248
|
+
query: str = typer.Argument(..., help="SQL query to execute and export"),
|
|
249
|
+
output_file: Path = typer.Argument(..., help="Output file path"),
|
|
250
|
+
profile: Optional[str] = typer.Option(
|
|
251
|
+
None, "--profile", help="Auth profile to use"
|
|
252
|
+
),
|
|
253
|
+
output_format: Optional[str] = typer.Option(
|
|
254
|
+
None,
|
|
255
|
+
"--format",
|
|
256
|
+
help="Output format (auto-detected from file extension if not specified)",
|
|
257
|
+
),
|
|
258
|
+
timeout: int = typer.Option(300, "--timeout", help="Query timeout in seconds"),
|
|
259
|
+
fallback_branches: Optional[str] = typer.Option(
|
|
260
|
+
None, "--fallback-branches", help="Comma-separated list of fallback branch IDs"
|
|
261
|
+
),
|
|
262
|
+
) -> None:
|
|
263
|
+
"""Execute a SQL query and export results to a file."""
|
|
264
|
+
console = Console()
|
|
265
|
+
formatter = OutputFormatter()
|
|
266
|
+
|
|
267
|
+
try:
|
|
268
|
+
# Auto-detect format from file extension if not specified
|
|
269
|
+
if output_format is None:
|
|
270
|
+
ext = output_file.suffix.lower()
|
|
271
|
+
if ext == ".json":
|
|
272
|
+
output_format = "json"
|
|
273
|
+
elif ext == ".csv":
|
|
274
|
+
output_format = "csv"
|
|
275
|
+
else:
|
|
276
|
+
output_format = "table" # Default
|
|
277
|
+
|
|
278
|
+
# Parse fallback branches if provided
|
|
279
|
+
fallback_branch_ids = (
|
|
280
|
+
fallback_branches.split(",") if fallback_branches else None
|
|
281
|
+
)
|
|
282
|
+
|
|
283
|
+
# Create service and execute query
|
|
284
|
+
service = SqlService(profile=profile)
|
|
285
|
+
|
|
286
|
+
with SpinnerProgressTracker().track_spinner("Executing SQL query..."):
|
|
287
|
+
result = service.execute_query(
|
|
288
|
+
query=query,
|
|
289
|
+
fallback_branch_ids=fallback_branch_ids,
|
|
290
|
+
timeout=timeout,
|
|
291
|
+
format="table" if output_format in ["table", "csv"] else "json",
|
|
292
|
+
)
|
|
293
|
+
|
|
294
|
+
# Save results to file
|
|
295
|
+
query_results = result.get("results", {})
|
|
296
|
+
formatter.save_to_file(query_results, output_file, output_format)
|
|
297
|
+
|
|
298
|
+
console.print(
|
|
299
|
+
f"[green]Query executed and results saved to {output_file}[/green]"
|
|
300
|
+
)
|
|
301
|
+
|
|
302
|
+
# Show query metadata
|
|
303
|
+
if "query_id" in result:
|
|
304
|
+
console.print(f"[dim]Query ID: {result['query_id']}[/dim]")
|
|
305
|
+
|
|
306
|
+
except Exception as e:
|
|
307
|
+
formatter.print_error(f"Failed to export query results: {e}")
|
|
308
|
+
raise typer.Exit(1)
|
|
309
|
+
|
|
310
|
+
|
|
311
|
+
@app.command("wait")
|
|
312
|
+
def wait_for_query(
|
|
313
|
+
query_id: str = typer.Argument(..., help="Query ID to wait for"),
|
|
314
|
+
profile: Optional[str] = typer.Option(
|
|
315
|
+
None, "--profile", help="Auth profile to use"
|
|
316
|
+
),
|
|
317
|
+
timeout: int = typer.Option(300, "--timeout", help="Maximum wait time in seconds"),
|
|
318
|
+
output_format: str = typer.Option(
|
|
319
|
+
"table", "--format", help="Output format for results (table, json, csv)"
|
|
320
|
+
),
|
|
321
|
+
output_file: Optional[Path] = typer.Option(
|
|
322
|
+
None, "--output", help="Save results to file when completed"
|
|
323
|
+
),
|
|
324
|
+
) -> None:
|
|
325
|
+
"""Wait for a query to complete and optionally display results."""
|
|
326
|
+
console = Console()
|
|
327
|
+
formatter = OutputFormatter()
|
|
328
|
+
|
|
329
|
+
try:
|
|
330
|
+
service = SqlService(profile=profile)
|
|
331
|
+
|
|
332
|
+
with SpinnerProgressTracker().track_spinner("Waiting for query to complete..."):
|
|
333
|
+
status_result = service.wait_for_completion(query_id, timeout)
|
|
334
|
+
|
|
335
|
+
console.print(f"Query ID: [bold]{query_id}[/bold]")
|
|
336
|
+
console.print(
|
|
337
|
+
f"Status: [green]{status_result.get('status', 'completed')}[/green]"
|
|
338
|
+
)
|
|
339
|
+
|
|
340
|
+
# Get and display results if requested
|
|
341
|
+
if output_file or output_format != "table":
|
|
342
|
+
with SpinnerProgressTracker().track_spinner("Retrieving results..."):
|
|
343
|
+
result = service.get_query_results(
|
|
344
|
+
query_id,
|
|
345
|
+
format="table" if output_format in ["table", "csv"] else "json",
|
|
346
|
+
)
|
|
347
|
+
|
|
348
|
+
if output_file:
|
|
349
|
+
formatter.save_to_file(result, output_file, output_format)
|
|
350
|
+
console.print(f"[green]Results saved to {output_file}[/green]")
|
|
351
|
+
else:
|
|
352
|
+
formatter.display(result, output_format)
|
|
353
|
+
else:
|
|
354
|
+
console.print("Use [bold]pltr sql results <query-id>[/bold] to get results")
|
|
355
|
+
|
|
356
|
+
except Exception as e:
|
|
357
|
+
formatter.print_error(f"Failed while waiting for query: {e}")
|
|
358
|
+
raise typer.Exit(1)
|
pltr/commands/verify.py
CHANGED
|
@@ -97,6 +97,7 @@ def verify(
|
|
|
97
97
|
"client_id": client_id,
|
|
98
98
|
"client_secret": client_secret,
|
|
99
99
|
},
|
|
100
|
+
timeout=30,
|
|
100
101
|
)
|
|
101
102
|
if token_response.status_code == 200:
|
|
102
103
|
access_token = token_response.json().get("access_token")
|
|
@@ -108,7 +109,7 @@ def verify(
|
|
|
108
109
|
raise typer.Exit(1)
|
|
109
110
|
|
|
110
111
|
# Make the request to /multipass/api/me
|
|
111
|
-
response = requests.get(url, headers=headers)
|
|
112
|
+
response = requests.get(url, headers=headers, timeout=30)
|
|
112
113
|
|
|
113
114
|
if response.status_code == 200:
|
|
114
115
|
user_info = response.json()
|
pltr/services/__init__.py
CHANGED
pltr/services/admin.py
ADDED
|
@@ -0,0 +1,314 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Admin service wrapper for Foundry SDK admin operations.
|
|
3
|
+
Provides a high-level interface for user, group, role, and organization management.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from typing import Any, Dict, Optional
|
|
7
|
+
import json
|
|
8
|
+
|
|
9
|
+
from .base import BaseService
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class AdminService(BaseService):
|
|
13
|
+
"""Service wrapper for Foundry admin operations."""
|
|
14
|
+
|
|
15
|
+
def _get_service(self) -> Any:
|
|
16
|
+
"""Get the Foundry admin service."""
|
|
17
|
+
return self.client.admin
|
|
18
|
+
|
|
19
|
+
# User Management Methods
|
|
20
|
+
def list_users(
|
|
21
|
+
self, page_size: Optional[int] = None, page_token: Optional[str] = None
|
|
22
|
+
) -> Dict[str, Any]:
|
|
23
|
+
"""
|
|
24
|
+
List all users in the organization.
|
|
25
|
+
|
|
26
|
+
Args:
|
|
27
|
+
page_size: Maximum number of users to return per page
|
|
28
|
+
page_token: Token for pagination (from previous response)
|
|
29
|
+
|
|
30
|
+
Returns:
|
|
31
|
+
Dictionary containing user list and pagination info
|
|
32
|
+
"""
|
|
33
|
+
try:
|
|
34
|
+
response = self.service.User.list(
|
|
35
|
+
page_size=page_size, page_token=page_token
|
|
36
|
+
)
|
|
37
|
+
return self._serialize_response(response)
|
|
38
|
+
except Exception as e:
|
|
39
|
+
raise RuntimeError(f"Failed to list users: {str(e)}")
|
|
40
|
+
|
|
41
|
+
def get_user(self, user_id: str) -> Dict[str, Any]:
|
|
42
|
+
"""
|
|
43
|
+
Get a specific user by ID.
|
|
44
|
+
|
|
45
|
+
Args:
|
|
46
|
+
user_id: The user ID or RID
|
|
47
|
+
|
|
48
|
+
Returns:
|
|
49
|
+
Dictionary containing user information
|
|
50
|
+
"""
|
|
51
|
+
try:
|
|
52
|
+
response = self.service.User.get(user_id)
|
|
53
|
+
return self._serialize_response(response)
|
|
54
|
+
except Exception as e:
|
|
55
|
+
raise RuntimeError(f"Failed to get user {user_id}: {str(e)}")
|
|
56
|
+
|
|
57
|
+
def get_current_user(self) -> Dict[str, Any]:
|
|
58
|
+
"""
|
|
59
|
+
Get information about the current authenticated user.
|
|
60
|
+
|
|
61
|
+
Returns:
|
|
62
|
+
Dictionary containing current user information
|
|
63
|
+
"""
|
|
64
|
+
try:
|
|
65
|
+
response = self.service.User.get_current()
|
|
66
|
+
return self._serialize_response(response)
|
|
67
|
+
except Exception as e:
|
|
68
|
+
raise RuntimeError(f"Failed to get current user: {str(e)}")
|
|
69
|
+
|
|
70
|
+
def search_users(
|
|
71
|
+
self,
|
|
72
|
+
query: str,
|
|
73
|
+
page_size: Optional[int] = None,
|
|
74
|
+
page_token: Optional[str] = None,
|
|
75
|
+
) -> Dict[str, Any]:
|
|
76
|
+
"""
|
|
77
|
+
Search for users by query string.
|
|
78
|
+
|
|
79
|
+
Args:
|
|
80
|
+
query: Search query string
|
|
81
|
+
page_size: Maximum number of users to return per page
|
|
82
|
+
page_token: Token for pagination (from previous response)
|
|
83
|
+
|
|
84
|
+
Returns:
|
|
85
|
+
Dictionary containing search results and pagination info
|
|
86
|
+
"""
|
|
87
|
+
try:
|
|
88
|
+
response = self.service.User.search(
|
|
89
|
+
query=query, page_size=page_size, page_token=page_token
|
|
90
|
+
)
|
|
91
|
+
return self._serialize_response(response)
|
|
92
|
+
except Exception as e:
|
|
93
|
+
raise RuntimeError(f"Failed to search users: {str(e)}")
|
|
94
|
+
|
|
95
|
+
def get_user_markings(self, user_id: str) -> Dict[str, Any]:
|
|
96
|
+
"""
|
|
97
|
+
Get markings/permissions for a specific user.
|
|
98
|
+
|
|
99
|
+
Args:
|
|
100
|
+
user_id: The user ID or RID
|
|
101
|
+
|
|
102
|
+
Returns:
|
|
103
|
+
Dictionary containing user markings information
|
|
104
|
+
"""
|
|
105
|
+
try:
|
|
106
|
+
response = self.service.User.get_markings(user_id)
|
|
107
|
+
return self._serialize_response(response)
|
|
108
|
+
except Exception as e:
|
|
109
|
+
raise RuntimeError(f"Failed to get user markings for {user_id}: {str(e)}")
|
|
110
|
+
|
|
111
|
+
def revoke_user_tokens(self, user_id: str) -> Dict[str, Any]:
|
|
112
|
+
"""
|
|
113
|
+
Revoke all tokens for a specific user.
|
|
114
|
+
|
|
115
|
+
Args:
|
|
116
|
+
user_id: The user ID or RID
|
|
117
|
+
|
|
118
|
+
Returns:
|
|
119
|
+
Dictionary containing operation result
|
|
120
|
+
"""
|
|
121
|
+
try:
|
|
122
|
+
self.service.User.revoke_all_tokens(user_id)
|
|
123
|
+
return {
|
|
124
|
+
"success": True,
|
|
125
|
+
"message": f"All tokens revoked for user {user_id}",
|
|
126
|
+
}
|
|
127
|
+
except Exception as e:
|
|
128
|
+
raise RuntimeError(f"Failed to revoke tokens for user {user_id}: {str(e)}")
|
|
129
|
+
|
|
130
|
+
# Group Management Methods
|
|
131
|
+
def list_groups(
|
|
132
|
+
self, page_size: Optional[int] = None, page_token: Optional[str] = None
|
|
133
|
+
) -> Dict[str, Any]:
|
|
134
|
+
"""
|
|
135
|
+
List all groups in the organization.
|
|
136
|
+
|
|
137
|
+
Args:
|
|
138
|
+
page_size: Maximum number of groups to return per page
|
|
139
|
+
page_token: Token for pagination (from previous response)
|
|
140
|
+
|
|
141
|
+
Returns:
|
|
142
|
+
Dictionary containing group list and pagination info
|
|
143
|
+
"""
|
|
144
|
+
try:
|
|
145
|
+
response = self.service.Group.list(
|
|
146
|
+
page_size=page_size, page_token=page_token
|
|
147
|
+
)
|
|
148
|
+
return self._serialize_response(response)
|
|
149
|
+
except Exception as e:
|
|
150
|
+
raise RuntimeError(f"Failed to list groups: {str(e)}")
|
|
151
|
+
|
|
152
|
+
def get_group(self, group_id: str) -> Dict[str, Any]:
|
|
153
|
+
"""
|
|
154
|
+
Get a specific group by ID.
|
|
155
|
+
|
|
156
|
+
Args:
|
|
157
|
+
group_id: The group ID or RID
|
|
158
|
+
|
|
159
|
+
Returns:
|
|
160
|
+
Dictionary containing group information
|
|
161
|
+
"""
|
|
162
|
+
try:
|
|
163
|
+
response = self.service.Group.get(group_id)
|
|
164
|
+
return self._serialize_response(response)
|
|
165
|
+
except Exception as e:
|
|
166
|
+
raise RuntimeError(f"Failed to get group {group_id}: {str(e)}")
|
|
167
|
+
|
|
168
|
+
def search_groups(
|
|
169
|
+
self,
|
|
170
|
+
query: str,
|
|
171
|
+
page_size: Optional[int] = None,
|
|
172
|
+
page_token: Optional[str] = None,
|
|
173
|
+
) -> Dict[str, Any]:
|
|
174
|
+
"""
|
|
175
|
+
Search for groups by query string.
|
|
176
|
+
|
|
177
|
+
Args:
|
|
178
|
+
query: Search query string
|
|
179
|
+
page_size: Maximum number of groups to return per page
|
|
180
|
+
page_token: Token for pagination (from previous response)
|
|
181
|
+
|
|
182
|
+
Returns:
|
|
183
|
+
Dictionary containing search results and pagination info
|
|
184
|
+
"""
|
|
185
|
+
try:
|
|
186
|
+
response = self.service.Group.search(
|
|
187
|
+
query=query, page_size=page_size, page_token=page_token
|
|
188
|
+
)
|
|
189
|
+
return self._serialize_response(response)
|
|
190
|
+
except Exception as e:
|
|
191
|
+
raise RuntimeError(f"Failed to search groups: {str(e)}")
|
|
192
|
+
|
|
193
|
+
def create_group(
|
|
194
|
+
self,
|
|
195
|
+
name: str,
|
|
196
|
+
description: Optional[str] = None,
|
|
197
|
+
organization_rid: Optional[str] = None,
|
|
198
|
+
) -> Dict[str, Any]:
|
|
199
|
+
"""
|
|
200
|
+
Create a new group.
|
|
201
|
+
|
|
202
|
+
Args:
|
|
203
|
+
name: The group name
|
|
204
|
+
description: Optional group description
|
|
205
|
+
organization_rid: Optional organization RID
|
|
206
|
+
|
|
207
|
+
Returns:
|
|
208
|
+
Dictionary containing created group information
|
|
209
|
+
"""
|
|
210
|
+
try:
|
|
211
|
+
# Build create request parameters
|
|
212
|
+
create_params = {"name": name}
|
|
213
|
+
if description:
|
|
214
|
+
create_params["description"] = description
|
|
215
|
+
if organization_rid:
|
|
216
|
+
create_params["organization_rid"] = organization_rid
|
|
217
|
+
|
|
218
|
+
response = self.service.Group.create(**create_params)
|
|
219
|
+
return self._serialize_response(response)
|
|
220
|
+
except Exception as e:
|
|
221
|
+
raise RuntimeError(f"Failed to create group '{name}': {str(e)}")
|
|
222
|
+
|
|
223
|
+
def delete_group(self, group_id: str) -> Dict[str, Any]:
|
|
224
|
+
"""
|
|
225
|
+
Delete a specific group.
|
|
226
|
+
|
|
227
|
+
Args:
|
|
228
|
+
group_id: The group ID or RID
|
|
229
|
+
|
|
230
|
+
Returns:
|
|
231
|
+
Dictionary containing operation result
|
|
232
|
+
"""
|
|
233
|
+
try:
|
|
234
|
+
self.service.Group.delete(group_id)
|
|
235
|
+
return {
|
|
236
|
+
"success": True,
|
|
237
|
+
"message": f"Group {group_id} deleted successfully",
|
|
238
|
+
}
|
|
239
|
+
except Exception as e:
|
|
240
|
+
raise RuntimeError(f"Failed to delete group {group_id}: {str(e)}")
|
|
241
|
+
|
|
242
|
+
# Organization Management Methods
|
|
243
|
+
def get_organization(self, organization_id: str) -> Dict[str, Any]:
|
|
244
|
+
"""
|
|
245
|
+
Get organization information.
|
|
246
|
+
|
|
247
|
+
Args:
|
|
248
|
+
organization_id: The organization ID or RID
|
|
249
|
+
|
|
250
|
+
Returns:
|
|
251
|
+
Dictionary containing organization information
|
|
252
|
+
"""
|
|
253
|
+
try:
|
|
254
|
+
response = self.service.Organization.get(organization_id)
|
|
255
|
+
return self._serialize_response(response)
|
|
256
|
+
except Exception as e:
|
|
257
|
+
raise RuntimeError(
|
|
258
|
+
f"Failed to get organization {organization_id}: {str(e)}"
|
|
259
|
+
)
|
|
260
|
+
|
|
261
|
+
# Role Management Methods
|
|
262
|
+
def get_role(self, role_id: str) -> Dict[str, Any]:
|
|
263
|
+
"""
|
|
264
|
+
Get role information.
|
|
265
|
+
|
|
266
|
+
Args:
|
|
267
|
+
role_id: The role ID or RID
|
|
268
|
+
|
|
269
|
+
Returns:
|
|
270
|
+
Dictionary containing role information
|
|
271
|
+
"""
|
|
272
|
+
try:
|
|
273
|
+
response = self.service.Role.get(role_id)
|
|
274
|
+
return self._serialize_response(response)
|
|
275
|
+
except Exception as e:
|
|
276
|
+
raise RuntimeError(f"Failed to get role {role_id}: {str(e)}")
|
|
277
|
+
|
|
278
|
+
def _serialize_response(self, response: Any) -> Dict[str, Any]:
|
|
279
|
+
"""
|
|
280
|
+
Convert response object to serializable dictionary.
|
|
281
|
+
|
|
282
|
+
Args:
|
|
283
|
+
response: Response object from SDK
|
|
284
|
+
|
|
285
|
+
Returns:
|
|
286
|
+
Serializable dictionary representation
|
|
287
|
+
"""
|
|
288
|
+
if response is None:
|
|
289
|
+
return {}
|
|
290
|
+
|
|
291
|
+
# Handle different response types
|
|
292
|
+
if hasattr(response, "dict"):
|
|
293
|
+
# Pydantic models
|
|
294
|
+
return response.dict()
|
|
295
|
+
elif hasattr(response, "__dict__"):
|
|
296
|
+
# Regular objects
|
|
297
|
+
result = {}
|
|
298
|
+
for key, value in response.__dict__.items():
|
|
299
|
+
if not key.startswith("_"):
|
|
300
|
+
try:
|
|
301
|
+
# Try to serialize the value
|
|
302
|
+
json.dumps(value)
|
|
303
|
+
result[key] = value
|
|
304
|
+
except (TypeError, ValueError):
|
|
305
|
+
# Convert non-serializable values to string
|
|
306
|
+
result[key] = str(value)
|
|
307
|
+
return result
|
|
308
|
+
else:
|
|
309
|
+
# Primitive types or already serializable
|
|
310
|
+
try:
|
|
311
|
+
json.dumps(response)
|
|
312
|
+
return response
|
|
313
|
+
except (TypeError, ValueError):
|
|
314
|
+
return {"data": str(response)}
|