pal-found-cli 0.1.2__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.
Files changed (89) hide show
  1. pal_found_cli/__init__.py +1 -0
  2. pal_found_cli/_version.py +24 -0
  3. pal_found_cli/admin/__init__.py +2 -0
  4. pal_found_cli/admin/scripts/__init__.py +2 -0
  5. pal_found_cli/admin/scripts/pal_found_admin_cli.py +600 -0
  6. pal_found_cli/aip_agents/__init__.py +1 -0
  7. pal_found_cli/aip_agents/metadata-allow-list.md +20 -0
  8. pal_found_cli/aip_agents/scripts/__init__.py +1 -0
  9. pal_found_cli/aip_agents/scripts/pal_found_aip_agents_cli.py +704 -0
  10. pal_found_cli/audit/__init__.py +1 -0
  11. pal_found_cli/audit/metadata-allow-list.md +4 -0
  12. pal_found_cli/audit/scripts/__init__.py +1 -0
  13. pal_found_cli/audit/scripts/pal_found_audit_cli.py +387 -0
  14. pal_found_cli/checkpoints/__init__.py +9 -0
  15. pal_found_cli/checkpoints/metadata-allow-list.md +7 -0
  16. pal_found_cli/checkpoints/scripts/__init__.py +1 -0
  17. pal_found_cli/checkpoints/scripts/pal_found_checkpoints_cli.py +527 -0
  18. pal_found_cli/common/__init__.py +56 -0
  19. pal_found_cli/common/access_control_guard.py +591 -0
  20. pal_found_cli/common/async_client_factory.py +161 -0
  21. pal_found_cli/common/auth_provider.py +82 -0
  22. pal_found_cli/common/binary_download_handler.py +288 -0
  23. pal_found_cli/common/config_loader.py +388 -0
  24. pal_found_cli/common/error_serializer.py +412 -0
  25. pal_found_cli/common/log_setup.py +241 -0
  26. pal_found_cli/common/metadata-allow-list.md +169 -0
  27. pal_found_cli/common/output_formatter.py +267 -0
  28. pal_found_cli/common/pagination_helper.py +212 -0
  29. pal_found_cli/common/retry.py +617 -0
  30. pal_found_cli/common/sdk_error_utils.py +112 -0
  31. pal_found_cli/common/session_manager.py +461 -0
  32. pal_found_cli/common/tracing_provider.py +107 -0
  33. pal_found_cli/connectivity/__init__.py +9 -0
  34. pal_found_cli/connectivity/metadata-allow-list.md +24 -0
  35. pal_found_cli/connectivity/scripts/__init__.py +1 -0
  36. pal_found_cli/connectivity/scripts/pal_found_connectivity_cli.py +720 -0
  37. pal_found_cli/data_health/__init__.py +9 -0
  38. pal_found_cli/data_health/metadata-allow-list.md +10 -0
  39. pal_found_cli/data_health/scripts/__init__.py +1 -0
  40. pal_found_cli/data_health/scripts/pal_found_data_health_cli.py +458 -0
  41. pal_found_cli/datasets/__init__.py +1 -0
  42. pal_found_cli/datasets/scripts/__init__.py +1 -0
  43. pal_found_cli/datasets/scripts/pal_found_datasets_cli.py +732 -0
  44. pal_found_cli/filesystem/__init__.py +2 -0
  45. pal_found_cli/filesystem/scripts/__init__.py +2 -0
  46. pal_found_cli/filesystem/scripts/pal_found_filesystem_cli.py +659 -0
  47. pal_found_cli/functions/__init__.py +2 -0
  48. pal_found_cli/functions/scripts/__init__.py +2 -0
  49. pal_found_cli/functions/scripts/pal_found_functions_cli.py +389 -0
  50. pal_found_cli/language_models/__init__.py +1 -0
  51. pal_found_cli/language_models/metadata-allow-list.md +6 -0
  52. pal_found_cli/language_models/scripts/__init__.py +1 -0
  53. pal_found_cli/language_models/scripts/pal_found_language_models_cli.py +342 -0
  54. pal_found_cli/media_sets/__init__.py +9 -0
  55. pal_found_cli/media_sets/metadata-allow-list.md +23 -0
  56. pal_found_cli/media_sets/scripts/__init__.py +1 -0
  57. pal_found_cli/media_sets/scripts/pal_found_media_sets_cli.py +642 -0
  58. pal_found_cli/models/__init__.py +5 -0
  59. pal_found_cli/models/metadata-allow-list.md +27 -0
  60. pal_found_cli/models/scripts/__init__.py +1 -0
  61. pal_found_cli/models/scripts/pal_found_models_cli.py +714 -0
  62. pal_found_cli/ontologies/__init__.py +1 -0
  63. pal_found_cli/ontologies/scripts/__init__.py +1 -0
  64. pal_found_cli/ontologies/scripts/pal_found_ontologies_cli.py +1225 -0
  65. pal_found_cli/orchestration/__init__.py +9 -0
  66. pal_found_cli/orchestration/metadata-allow-list.md +24 -0
  67. pal_found_cli/orchestration/scripts/__init__.py +1 -0
  68. pal_found_cli/orchestration/scripts/pal_found_orchestration_cli.py +640 -0
  69. pal_found_cli/sql_queries/__init__.py +9 -0
  70. pal_found_cli/sql_queries/metadata-allow-list.md +9 -0
  71. pal_found_cli/sql_queries/scripts/__init__.py +1 -0
  72. pal_found_cli/sql_queries/scripts/pal_found_sql_queries_cli.py +514 -0
  73. pal_found_cli/streams/__init__.py +9 -0
  74. pal_found_cli/streams/metadata-allow-list.md +19 -0
  75. pal_found_cli/streams/scripts/__init__.py +1 -0
  76. pal_found_cli/streams/scripts/pal_found_streams_cli.py +615 -0
  77. pal_found_cli/third_party_applications/__init__.py +9 -0
  78. pal_found_cli/third_party_applications/metadata-allow-list.md +13 -0
  79. pal_found_cli/third_party_applications/scripts/__init__.py +1 -0
  80. pal_found_cli/third_party_applications/scripts/pal_found_third_party_applications_cli.py +547 -0
  81. pal_found_cli/widgets/__init__.py +9 -0
  82. pal_found_cli/widgets/metadata-allow-list.md +12 -0
  83. pal_found_cli/widgets/scripts/__init__.py +1 -0
  84. pal_found_cli/widgets/scripts/pal_found_widgets_cli.py +571 -0
  85. pal_found_cli-0.1.2.dist-info/METADATA +74 -0
  86. pal_found_cli-0.1.2.dist-info/RECORD +89 -0
  87. pal_found_cli-0.1.2.dist-info/WHEEL +5 -0
  88. pal_found_cli-0.1.2.dist-info/entry_points.txt +19 -0
  89. pal_found_cli-0.1.2.dist-info/top_level.txt +1 -0
@@ -0,0 +1 @@
1
+ """Foundry SQL Queries CLI scripts package."""
@@ -0,0 +1,514 @@
1
+ #!/usr/bin/env python3
2
+ """Foundry SQL Queries CLI - 5 canonical SqlQueries API v2 operations.
3
+
4
+ Exposes exactly 5 public ``foundry_sdk.v2.sql_queries`` operations as CLI
5
+ subcommands through the single ``SqlQuery`` client path:
6
+
7
+ - sql-query (1): cancel, execute, execute-ontology, get-results, get-status
8
+
9
+ The CLI resource subcommand is ``query`` (kebab-case of the ``sql_query``
10
+ SDK resource). Catalog keys and ACL paths keep the ``sql_query`` snake_case
11
+ name.
12
+
13
+ Usage: pal-found-sql-queries <resource> <operation> [options]
14
+ Output: JSON/TOON on stdout, NDJSON diagnostics on stderr (ADR-004, ADR-005).
15
+ Exit codes per ADR-001 taxonomy.
16
+ Access control: AccessControlGuard before client construction and file
17
+ effects (SRS 4.2, ADR-007). Retry: transient conditions only per ADR-002.
18
+ Tracing: SDK-native B3 via invocation_scope; include_attribution=False.
19
+ Downloads: bounded atomic persistence via BinaryDownloadHandler (DESIGN-005)
20
+ for the two Arrow-byte operations.
21
+ """
22
+
23
+ from __future__ import annotations
24
+
25
+ import argparse
26
+ import asyncio
27
+ import json
28
+ import logging
29
+ import sys
30
+ from pathlib import Path
31
+ from typing import Any, NoReturn
32
+
33
+ from pal_found_cli.common.access_control_guard import (
34
+ AccessControlError,
35
+ AccessControlGuard,
36
+ )
37
+ from pal_found_cli.common.async_client_factory import AsyncClientFactory
38
+ from pal_found_cli.common.binary_download_handler import BinaryDownloadHandler
39
+ from pal_found_cli.common.config_loader import ConfigLoader, ConfigurationError
40
+ from pal_found_cli.common.error_serializer import (
41
+ EXIT_ACCESS_CONTROL,
42
+ EXIT_AUTH,
43
+ EXIT_CONFIGURATION,
44
+ EXIT_NOT_FOUND,
45
+ EXIT_PERMISSION_DENIED,
46
+ EXIT_RATE_LIMIT,
47
+ EXIT_SERVER_ERROR,
48
+ EXIT_SUCCESS,
49
+ EXIT_TIMEOUT,
50
+ EXIT_USER_INPUT,
51
+ ErrorSerializer,
52
+ )
53
+ from pal_found_cli.common.log_setup import LogSetup
54
+ from pal_found_cli.common.output_formatter import OutputFormatter
55
+ from pal_found_cli.common.retry import RetryHandler
56
+ from pal_found_cli.common.sdk_error_utils import sdk_exception_exit_code, sdk_http_status
57
+
58
+ logger = logging.getLogger(__name__)
59
+
60
+ OperationSpec = dict[str, Any]
61
+
62
+ _METADATA_ALLOWLIST_PATH = (
63
+ Path(__file__).resolve().parents[1] / "metadata-allow-list.md"
64
+ )
65
+
66
+ # The two file-producing Arrow byte operations (DESIGN-015). Both acquire a
67
+ # streaming SDK response and persist bounded content through
68
+ # BinaryDownloadHandler before opening the destination.
69
+ DOWNLOAD_OPS: frozenset[tuple[str, str]] = frozenset(
70
+ {
71
+ ("sql_query", "execute_ontology"),
72
+ ("sql_query", "get_results"),
73
+ }
74
+ )
75
+
76
+
77
+ class CLIInputError(ValueError):
78
+ """A locally generated input error whose message never echoes values."""
79
+
80
+ exit_code = EXIT_USER_INPUT
81
+
82
+
83
+ class _ArgumentParser(argparse.ArgumentParser):
84
+ """Raise parser failures into the structured error boundary."""
85
+
86
+ def error(self, message: str) -> NoReturn:
87
+ raise CLIInputError("Invalid command input")
88
+
89
+
90
+ def _op(
91
+ resource: str,
92
+ operation: str,
93
+ client_path: tuple[str, ...],
94
+ method: str,
95
+ positional: tuple[str, ...] = (),
96
+ required: tuple[str, ...] = (),
97
+ optional: tuple[str, ...] = (),
98
+ *,
99
+ json_args: frozenset[str] = frozenset(),
100
+ list_json_args: frozenset[str] = frozenset(),
101
+ ) -> OperationSpec:
102
+ """Build one operation specification entry."""
103
+ return {
104
+ "resource": resource,
105
+ "operation": operation,
106
+ "client_path": client_path,
107
+ "method": method,
108
+ "positional": positional,
109
+ "required": required,
110
+ "optional": optional,
111
+ "json_args": json_args,
112
+ "list_json_args": list_json_args,
113
+ }
114
+
115
+
116
+ OP_SPECS: tuple[OperationSpec, ...] = (
117
+ _op(
118
+ "sql_query",
119
+ "cancel",
120
+ ("SqlQuery",),
121
+ "cancel",
122
+ ("sql_query_id",),
123
+ ),
124
+ _op(
125
+ "sql_query",
126
+ "execute",
127
+ ("SqlQuery",),
128
+ "execute",
129
+ required=("query",),
130
+ optional=("fallback_branch_ids",),
131
+ list_json_args=frozenset({"fallback_branch_ids"}),
132
+ ),
133
+ _op(
134
+ "sql_query",
135
+ "execute_ontology",
136
+ ("SqlQuery",),
137
+ "execute_ontology",
138
+ required=("query",),
139
+ optional=("dry_run", "parameters", "row_limit"),
140
+ json_args=frozenset({"parameters"}),
141
+ ),
142
+ _op(
143
+ "sql_query",
144
+ "get_results",
145
+ ("SqlQuery",),
146
+ "get_results",
147
+ ("sql_query_id",),
148
+ optional=("output",),
149
+ ),
150
+ _op(
151
+ "sql_query",
152
+ "get_status",
153
+ ("SqlQuery",),
154
+ "get_status",
155
+ ("sql_query_id",),
156
+ ),
157
+ )
158
+
159
+ OPERATION_BY_RESOURCE: dict[str, dict[str, OperationSpec]] = {}
160
+ for _spec in OP_SPECS:
161
+ OPERATION_BY_RESOURCE.setdefault(_spec["resource"], {})[_spec["operation"]] = _spec
162
+
163
+ # DESIGN-015 CLI contract: the ``sql_query`` resource is exposed as the
164
+ # ``query`` subcommand (kebab of ``sql_query`` would be ``sql-query``). The
165
+ # canonical ACL/env keys keep the snake_case ``sql_query`` name.
166
+ _CLI_RESOURCE_NAMES: dict[str, str] = {"sql_query": "query"}
167
+
168
+
169
+ # Structured JSON arguments across the catalog use the ``-json`` flag suffix.
170
+ _JSON_ARG_NAMES: frozenset[str] = frozenset(
171
+ name
172
+ for spec in OP_SPECS
173
+ for name in (*spec["json_args"], *spec["list_json_args"])
174
+ )
175
+
176
+ # Integer-typed SDK options.
177
+ _INT_ARG_NAMES: frozenset[str] = frozenset({"row_limit"})
178
+
179
+ # Boolean-typed SDK options registered as store_true flags.
180
+ _BOOLEAN_ARG_NAMES: frozenset[str] = frozenset({"dry_run"})
181
+
182
+
183
+ def _common_parser() -> _ArgumentParser:
184
+ """Build operation-level common options."""
185
+ parser = _ArgumentParser(add_help=False)
186
+ parser.add_argument("--timeout", type=int, default=None)
187
+ parser.add_argument("--format", choices=("json", "toon", "auto"), default="auto")
188
+ parser.add_argument("--pretty", action="store_true")
189
+ return parser
190
+
191
+
192
+ def _kebab(name: str) -> str:
193
+ """Convert snake_case names to CLI kebab-case."""
194
+ return name.replace("_", "-")
195
+
196
+
197
+ def _cli_resource(resource: str) -> str:
198
+ """Return the CLI subcommand name for a canonical resource key."""
199
+ return _CLI_RESOURCE_NAMES.get(resource, _kebab(resource))
200
+
201
+
202
+ def _resource_from_cli(cli_name: str) -> str:
203
+ """Resolve a CLI subcommand back to the canonical resource key."""
204
+ for canonical, name in _CLI_RESOURCE_NAMES.items():
205
+ if name == cli_name:
206
+ return canonical
207
+ return cli_name.replace("-", "_")
208
+
209
+
210
+ def _add_kwarg(
211
+ parser: argparse.ArgumentParser, arg_name: str, *, required: bool
212
+ ) -> None:
213
+ """Add one named operation argument."""
214
+ if arg_name in _JSON_ARG_NAMES:
215
+ flag = "--" + _kebab(arg_name) + "-json"
216
+ elif arg_name in _BOOLEAN_ARG_NAMES:
217
+ flag = "--" + _kebab(arg_name)
218
+ parser.add_argument(flag, action="store_true", default=None, dest=arg_name)
219
+ return
220
+ else:
221
+ flag = "--" + _kebab(arg_name)
222
+ kwargs: dict[str, Any] = {"required": required, "default": None, "dest": arg_name}
223
+ if arg_name in _INT_ARG_NAMES:
224
+ kwargs["type"] = int
225
+ parser.add_argument(flag, **kwargs)
226
+
227
+
228
+ def build_parser() -> argparse.ArgumentParser:
229
+ """Build argparse parser with all 5 SqlQueries operations."""
230
+ parser = _ArgumentParser(
231
+ prog="pal-found-sql-queries",
232
+ description="Foundry SQL Queries CLI - 5 SqlQueries API v2 operations",
233
+ )
234
+ subparsers = parser.add_subparsers(dest="resource", help="Resource type")
235
+
236
+ for resource in sorted(OPERATION_BY_RESOURCE):
237
+ res_parser = subparsers.add_parser(_cli_resource(resource))
238
+ op_sub = res_parser.add_subparsers(dest="operation")
239
+ for operation, spec in sorted(OPERATION_BY_RESOURCE[resource].items()):
240
+ op_parser = op_sub.add_parser(
241
+ _kebab(operation), parents=[_common_parser()]
242
+ )
243
+ for arg_name in spec["positional"]:
244
+ op_parser.add_argument(arg_name)
245
+ for arg_name in spec["required"]:
246
+ _add_kwarg(op_parser, arg_name, required=True)
247
+ for arg_name in spec["optional"]:
248
+ _add_kwarg(op_parser, arg_name, required=False)
249
+
250
+ return parser
251
+
252
+
253
+ def _spec_for(resource: str, operation: str) -> OperationSpec:
254
+ """Return one catalog operation specification."""
255
+ try:
256
+ return OPERATION_BY_RESOURCE[resource][operation]
257
+ except KeyError as exc:
258
+ raise CLIInputError(f"Unknown operation: {resource}.{operation}") from exc
259
+
260
+
261
+ def _get_client(root_client: Any, client_path: tuple[str, ...]) -> Any:
262
+ """Resolve the exact public nested SqlQueries SDK resource."""
263
+ client = root_client.sql_queries
264
+ for attribute in client_path:
265
+ client = getattr(client, attribute)
266
+ return client
267
+
268
+
269
+ def _required_text(value: Any, *, field: str) -> str:
270
+ """Validate a non-empty string without echoing its value."""
271
+ if not isinstance(value, str) or not value.strip():
272
+ raise CLIInputError(f"{field} must not be empty")
273
+ return value
274
+
275
+
276
+ def _decode_json(value: str, *, field: str) -> Any:
277
+ try:
278
+ return json.loads(value)
279
+ except json.JSONDecodeError as exc:
280
+ raise CLIInputError(f"{field} must contain valid JSON") from exc
281
+
282
+
283
+ def _parse_json_object(value: str, *, field: str) -> dict[str, Any]:
284
+ """Decode one JSON object."""
285
+ result = _decode_json(value, field=field)
286
+ if not isinstance(result, dict):
287
+ raise CLIInputError(f"{field} must be a JSON object")
288
+ return result
289
+
290
+
291
+ def _parse_json_string_list(value: str, *, field: str) -> list[str]:
292
+ """Decode one JSON array containing only strings."""
293
+ result = _decode_json(value, field=field)
294
+ if not isinstance(result, list) or not all(
295
+ isinstance(item, str) for item in result
296
+ ):
297
+ raise CLIInputError(f"{field} must be a JSON array of strings")
298
+ return result
299
+
300
+
301
+ def _validate_timeout(value: int) -> int:
302
+ """Validate the effective per-attempt timeout from ADR-002."""
303
+ if isinstance(value, bool) or not isinstance(value, int) or not 1 <= value <= 3600:
304
+ raise CLIInputError("timeout must be between 1 and 3600 seconds")
305
+ return value
306
+
307
+
308
+ def _validate_inputs(spec: OperationSpec, args: argparse.Namespace) -> None:
309
+ """Validate scalars and decode JSON arguments before client creation."""
310
+ for name in (*spec["positional"], *spec["required"]):
311
+ value = getattr(args, name, None)
312
+ if isinstance(value, str):
313
+ _required_text(value, field=name)
314
+ for name in spec["json_args"]:
315
+ value = getattr(args, name, None)
316
+ if value is not None:
317
+ setattr(args, name, _parse_json_object(value, field=name))
318
+ for name in spec["list_json_args"]:
319
+ value = getattr(args, name, None)
320
+ if value is not None:
321
+ setattr(args, name, _parse_json_string_list(value, field=name))
322
+
323
+
324
+ def _model_to_dict(value: Any) -> Any:
325
+ """Convert SDK models to JSON-compatible values."""
326
+ if value is None:
327
+ return None
328
+ if isinstance(value, list):
329
+ return [_model_to_dict(item) for item in value]
330
+ if isinstance(value, dict):
331
+ return {key: _model_to_dict(item) for key, item in value.items()}
332
+ if isinstance(value, bytes):
333
+ return {
334
+ "content_type": "application/octet-stream",
335
+ "byte_count": len(value),
336
+ "preview": value[:16].hex(),
337
+ }
338
+ for method_name in ("to_dict", "model_dump", "dict"):
339
+ method = getattr(value, method_name, None)
340
+ if callable(method):
341
+ result = method()
342
+ if isinstance(result, dict):
343
+ return {key: _model_to_dict(item) for key, item in result.items()}
344
+ return value
345
+
346
+
347
+ def _build_kwargs(
348
+ spec: OperationSpec, args: argparse.Namespace, timeout: int
349
+ ) -> dict[str, Any]:
350
+ """Build SDK keyword arguments, omitting absent optional values."""
351
+ kwargs: dict[str, Any] = {}
352
+ for name in (*spec["required"], *spec["optional"]):
353
+ value = getattr(args, name, None)
354
+ if value is not None:
355
+ kwargs[name] = value
356
+ kwargs["request_timeout"] = timeout
357
+ return kwargs
358
+
359
+
360
+ async def _invoke(
361
+ spec: OperationSpec,
362
+ client: Any,
363
+ args: argparse.Namespace,
364
+ timeout: int,
365
+ ) -> Any:
366
+ """Invoke one non-downloaded SDK operation."""
367
+ positional = [getattr(args, name) for name in spec["positional"]]
368
+ return await getattr(client, spec["method"])(
369
+ *positional, **_build_kwargs(spec, args, timeout)
370
+ )
371
+
372
+
373
+ async def _download_operation(
374
+ spec: OperationSpec,
375
+ client: Any,
376
+ args: argparse.Namespace,
377
+ timeout: int,
378
+ cfg: ConfigLoader,
379
+ ) -> dict[str, Any]:
380
+ """Stream one bounded Arrow download through BinaryDownloadHandler."""
381
+ method = getattr(getattr(client, "with_streaming_response"), spec["method"])
382
+ positional = [getattr(args, name) for name in spec["positional"]]
383
+ kwargs: dict[str, Any] = {}
384
+ for name in spec["optional"]:
385
+ if name == "output":
386
+ continue
387
+ value = getattr(args, name, None)
388
+ if value is not None:
389
+ kwargs[name] = value
390
+ response_context = method(*positional, request_timeout=timeout, **kwargs)
391
+ async with response_context as response:
392
+ result = await BinaryDownloadHandler(config=cfg).save(
393
+ response.aiter_bytes(),
394
+ original_filename=getattr(args, "output", None),
395
+ namespace="sql_queries",
396
+ operation=f"{spec['resource']}.{spec['method']}",
397
+ content_length=None,
398
+ content_encoding=None,
399
+ mime_type=None,
400
+ )
401
+ return result.to_dict()
402
+
403
+
404
+ def _serialize_error(exception: BaseException) -> int:
405
+ """Write one privacy-safe ADR-001 error envelope to stdout."""
406
+ serializer = ErrorSerializer()
407
+ status = sdk_http_status(exception)
408
+ declared = getattr(exception, "exit_code", None)
409
+ sdk_exit = sdk_exception_exit_code(exception)
410
+ if isinstance(declared, int) and 1 <= declared <= 9:
411
+ exit_code = declared
412
+ elif sdk_exit is not None:
413
+ exit_code = sdk_exit
414
+ elif status == 401:
415
+ exit_code = EXIT_AUTH
416
+ elif status == 403:
417
+ exit_code = EXIT_PERMISSION_DENIED
418
+ elif status == 404:
419
+ exit_code = EXIT_NOT_FOUND
420
+ elif status == 429:
421
+ exit_code = EXIT_RATE_LIMIT
422
+ elif isinstance(exception, ConfigurationError):
423
+ exit_code = EXIT_CONFIGURATION
424
+ elif isinstance(exception, (TypeError, ValueError)):
425
+ exit_code = EXIT_USER_INPUT
426
+ elif isinstance(exception, TimeoutError):
427
+ exit_code = EXIT_TIMEOUT
428
+ else:
429
+ exit_code = EXIT_SERVER_ERROR
430
+
431
+ safe = isinstance(
432
+ exception,
433
+ (CLIInputError, ConfigurationError, TimeoutError),
434
+ ) or declared in {EXIT_ACCESS_CONTROL, EXIT_CONFIGURATION}
435
+ message = str(exception) if safe else "SqlQueries operation failed"
436
+ envelope = serializer.create_error_envelope(
437
+ exit_code, message, type(exception).__name__, serializer.call_id
438
+ )
439
+ if isinstance(status, int):
440
+ envelope["http_status"] = status
441
+ sys.stdout.write(json.dumps(envelope, default=str) + "\n")
442
+ sys.stdout.flush()
443
+ logger.error(
444
+ "SqlQueries operation failed",
445
+ extra={
446
+ "call_id": serializer.call_id,
447
+ "exception_type": type(exception).__name__,
448
+ "http_status": status,
449
+ },
450
+ )
451
+ return exit_code
452
+
453
+
454
+ async def main() -> int:
455
+ """Run one stateless Foundry SqlQueries invocation."""
456
+ parser = build_parser()
457
+ try:
458
+ args = parser.parse_args()
459
+ if not args.resource or not getattr(args, "operation", None):
460
+ raise CLIInputError("a SqlQueries operation is required")
461
+ except CLIInputError as exc:
462
+ return _serialize_error(exc)
463
+
464
+ try:
465
+ cfg = ConfigLoader()
466
+ cfg.load()
467
+ LogSetup.configure(log_level=cfg.log_level)
468
+
469
+ resource = _resource_from_cli(args.resource)
470
+ operation = args.operation.replace("-", "_")
471
+ spec = _spec_for(resource, operation)
472
+ _validate_inputs(spec, args)
473
+ timeout = _validate_timeout(
474
+ args.timeout if args.timeout is not None else cfg.timeout_s
475
+ )
476
+ AccessControlGuard(
477
+ cfg,
478
+ "SQL_QUERIES",
479
+ metadata_allowlist_path=str(_METADATA_ALLOWLIST_PATH),
480
+ ).check(resource, operation)
481
+
482
+ factory = AsyncClientFactory()
483
+ with factory.invocation_scope(cfg, include_attribution=False):
484
+ root_client = factory.create(cfg, include_attribution=False)
485
+ client = _get_client(root_client, spec["client_path"])
486
+ retry_handler = RetryHandler(timeout_s=timeout)
487
+ if (resource, operation) in DOWNLOAD_OPS:
488
+ result = await retry_handler.execute(
489
+ _download_operation, spec, client, args, timeout, cfg
490
+ )
491
+ else:
492
+ result = await retry_handler.execute(
493
+ _invoke, spec, client, args, timeout
494
+ )
495
+
496
+ formatter = OutputFormatter(format_setting=args.format, pretty=args.pretty)
497
+ print(formatter.format(_model_to_dict(result)))
498
+ return EXIT_SUCCESS
499
+ except AccessControlError as exc:
500
+ ErrorSerializer().serialize(exc)
501
+ return EXIT_ACCESS_CONTROL
502
+ except asyncio.CancelledError:
503
+ return _serialize_error(TimeoutError("Operation cancelled"))
504
+ except Exception as exc:
505
+ return _serialize_error(exc)
506
+
507
+
508
+ def console_main() -> int:
509
+ """Run the async CLI through one event-loop boundary."""
510
+ return asyncio.run(main())
511
+
512
+
513
+ if __name__ == "__main__":
514
+ raise SystemExit(console_main())
@@ -0,0 +1,9 @@
1
+ """Foundry Streams CLI package."""
2
+
3
+ from pal_found_cli.streams.scripts.pal_found_streams_cli import (
4
+ build_parser,
5
+ console_main,
6
+ main,
7
+ )
8
+
9
+ __all__ = ["build_parser", "console_main", "main"]
@@ -0,0 +1,19 @@
1
+ # Streams metadata allow-list
2
+
3
+ | SDK Path | Status | Rationale |
4
+ | --- | --- | --- |
5
+ | `streams.dataset.create` | BLOCKED | Creates a streaming dataset |
6
+ | `streams.stream.create` | BLOCKED | Creates a stream |
7
+ | `streams.stream.get` | PERMITTED | Stream metadata |
8
+ | `streams.stream.get_end_offsets` | PERMITTED | Offset metadata |
9
+ | `streams.stream.get_records` | BLOCKED | Streams record content bytes |
10
+ | `streams.stream.publish_binary_record` | BLOCKED | Publishes record content |
11
+ | `streams.stream.publish_record` | BLOCKED | Publishes record content |
12
+ | `streams.stream.publish_records` | BLOCKED | Publishes record content |
13
+ | `streams.stream.reset` | BLOCKED | Resets stream state |
14
+ | `streams.subscriber.commit_offsets` | BLOCKED | Mutates subscriber offsets |
15
+ | `streams.subscriber.create` | BLOCKED | Creates a subscriber |
16
+ | `streams.subscriber.delete` | BLOCKED | Deletes a subscriber |
17
+ | `streams.subscriber.get_read_position` | PERMITTED | Subscriber offset metadata |
18
+ | `streams.subscriber.read_records` | BLOCKED | Streams record content bytes |
19
+ | `streams.subscriber.reset_offsets` | BLOCKED | Mutates subscriber offsets |
@@ -0,0 +1 @@
1
+ """Foundry Streams CLI scripts package."""