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,9 @@
1
+ """Foundry Data Health CLI package."""
2
+
3
+ from pal_found_cli.data_health.scripts.pal_found_data_health_cli import (
4
+ build_parser,
5
+ console_main,
6
+ main,
7
+ )
8
+
9
+ __all__ = ["build_parser", "console_main", "main"]
@@ -0,0 +1,10 @@
1
+ # DataHealth metadata allow-list
2
+
3
+ | SDK Path | Status | Rationale |
4
+ | --- | --- | --- |
5
+ | `data_health.check.create` | BLOCKED | Creates a check |
6
+ | `data_health.check.delete` | BLOCKED | Deletes a check |
7
+ | `data_health.check.get` | PERMITTED | Single check metadata |
8
+ | `data_health.check.replace` | BLOCKED | Replaces a check configuration |
9
+ | `data_health.check_report.get` | PERMITTED | Single check report metadata |
10
+ | `data_health.check_report.get_latest` | PERMITTED | Latest check report metadata |
@@ -0,0 +1 @@
1
+ """Foundry Data Health CLI scripts package."""
@@ -0,0 +1,458 @@
1
+ #!/usr/bin/env python3
2
+ """Foundry Data Health CLI - 6 canonical Data Health API v2 operations.
3
+
4
+ Exposes exactly 6 public ``foundry_sdk.v2.data_health`` operations as CLI
5
+ subcommands across the ``Check`` client and its nested ``CheckReport``
6
+ client:
7
+
8
+ - check (4): create, delete, get, replace
9
+ - check-report (2): get, get-latest
10
+
11
+ ``check_report`` operations dispatch through the nested
12
+ ``Check.CheckReport`` accessor. ``check_report get_latest`` takes an integer
13
+ ``--limit`` (default 10, maximum 100) that bounds a single response; it is
14
+ not a cursor and never routes through PaginationHelper.
15
+
16
+ The write set is ``check.create``, ``check.delete``, and ``check.replace``
17
+ (replace inherits the replace-class write classification). The packaged
18
+ metadata-only policy permits exactly 3 operations (check.get,
19
+ check_report.get, check_report.get_latest) and blocks the remaining 3.
20
+
21
+ Usage: pal-found-data-health <resource> <operation> [options]
22
+ Output: JSON/TOON on stdout, NDJSON diagnostics on stderr (ADR-004, ADR-005).
23
+ Exit codes per ADR-001 taxonomy.
24
+ Access control: AccessControlGuard before client construction (SRS 4.2,
25
+ ADR-007). Retry: transient conditions only per ADR-002; at-least-once
26
+ behavior is disclosed because retrying create/replace can duplicate checks
27
+ or re-run validation.
28
+ Tracing: SDK-native B3 via invocation_scope; include_attribution=False
29
+ (namespace is outside FR-ATTR-4 scope).
30
+ """
31
+
32
+ from __future__ import annotations
33
+
34
+ import argparse
35
+ import asyncio
36
+ import json
37
+ import logging
38
+ import sys
39
+ from pathlib import Path
40
+ from typing import Any, NoReturn
41
+
42
+ from pal_found_cli.common.access_control_guard import (
43
+ AccessControlError,
44
+ AccessControlGuard,
45
+ )
46
+ from pal_found_cli.common.async_client_factory import AsyncClientFactory
47
+ from pal_found_cli.common.config_loader import ConfigLoader, ConfigurationError
48
+ from pal_found_cli.common.error_serializer import (
49
+ EXIT_ACCESS_CONTROL,
50
+ EXIT_AUTH,
51
+ EXIT_CONFIGURATION,
52
+ EXIT_NOT_FOUND,
53
+ EXIT_PERMISSION_DENIED,
54
+ EXIT_RATE_LIMIT,
55
+ EXIT_SERVER_ERROR,
56
+ EXIT_SUCCESS,
57
+ EXIT_TIMEOUT,
58
+ EXIT_USER_INPUT,
59
+ ErrorSerializer,
60
+ )
61
+ from pal_found_cli.common.log_setup import LogSetup
62
+ from pal_found_cli.common.output_formatter import OutputFormatter
63
+ from pal_found_cli.common.retry import RetryHandler
64
+ from pal_found_cli.common.sdk_error_utils import sdk_exception_exit_code, sdk_http_status
65
+
66
+ logger = logging.getLogger(__name__)
67
+
68
+ OperationSpec = dict[str, Any]
69
+
70
+ _METADATA_ALLOWLIST_PATH = (
71
+ Path(__file__).resolve().parents[1] / "metadata-allow-list.md"
72
+ )
73
+
74
+
75
+ class CLIInputError(ValueError):
76
+ """A locally generated input error whose message never echoes values."""
77
+
78
+ exit_code = EXIT_USER_INPUT
79
+
80
+
81
+ class _ArgumentParser(argparse.ArgumentParser):
82
+ """Raise parser failures into the structured error boundary."""
83
+
84
+ def error(self, message: str) -> NoReturn:
85
+ raise CLIInputError("Invalid command input")
86
+
87
+
88
+ def _op(
89
+ resource: str,
90
+ operation: str,
91
+ client_path: tuple[str, ...],
92
+ method: str,
93
+ positional: tuple[str, ...] = (),
94
+ required: tuple[str, ...] = (),
95
+ optional: tuple[str, ...] = (),
96
+ *,
97
+ json_args: frozenset[str] = frozenset(),
98
+ list_json_args: frozenset[str] = frozenset(),
99
+ ) -> OperationSpec:
100
+ """Build one operation specification entry."""
101
+ return {
102
+ "resource": resource,
103
+ "operation": operation,
104
+ "client_path": client_path,
105
+ "method": method,
106
+ "positional": positional,
107
+ "required": required,
108
+ "optional": optional,
109
+ "json_args": json_args,
110
+ "list_json_args": list_json_args,
111
+ }
112
+
113
+
114
+ OP_SPECS: tuple[OperationSpec, ...] = (
115
+ _op(
116
+ "check",
117
+ "create",
118
+ ("Check",),
119
+ "create",
120
+ required=("config",),
121
+ optional=("intent",),
122
+ json_args=frozenset({"config"}),
123
+ ),
124
+ _op(
125
+ "check",
126
+ "delete",
127
+ ("Check",),
128
+ "delete",
129
+ ("check_rid",),
130
+ ),
131
+ _op(
132
+ "check",
133
+ "get",
134
+ ("Check",),
135
+ "get",
136
+ ("check_rid",),
137
+ ),
138
+ _op(
139
+ "check",
140
+ "replace",
141
+ ("Check",),
142
+ "replace",
143
+ ("check_rid",),
144
+ required=("config",),
145
+ optional=("intent",),
146
+ json_args=frozenset({"config"}),
147
+ ),
148
+ _op(
149
+ "check_report",
150
+ "get",
151
+ ("Check", "CheckReport"),
152
+ "get",
153
+ ("check_rid", "check_report_rid"),
154
+ ),
155
+ _op(
156
+ "check_report",
157
+ "get_latest",
158
+ ("Check", "CheckReport"),
159
+ "get_latest",
160
+ ("check_rid",),
161
+ optional=("limit",),
162
+ ),
163
+ )
164
+
165
+ OPERATION_BY_RESOURCE: dict[str, dict[str, OperationSpec]] = {}
166
+ for _spec in OP_SPECS:
167
+ OPERATION_BY_RESOURCE.setdefault(_spec["resource"], {})[_spec["operation"]] = _spec
168
+
169
+
170
+ # Structured JSON arguments across the catalog use the ``-json`` flag suffix.
171
+ _JSON_ARG_NAMES: frozenset[str] = frozenset(
172
+ name
173
+ for spec in OP_SPECS
174
+ for name in (*spec["json_args"], *spec["list_json_args"])
175
+ )
176
+
177
+ # Integer-typed SDK options.
178
+ _INT_ARG_NAMES: frozenset[str] = frozenset({"limit"})
179
+
180
+
181
+ def _common_parser() -> _ArgumentParser:
182
+ """Build operation-level common options."""
183
+ parser = _ArgumentParser(add_help=False)
184
+ parser.add_argument("--timeout", type=int, default=None)
185
+ parser.add_argument("--format", choices=("json", "toon", "auto"), default="auto")
186
+ parser.add_argument("--pretty", action="store_true")
187
+ return parser
188
+
189
+
190
+ def _kebab(name: str) -> str:
191
+ """Convert snake_case names to CLI kebab-case."""
192
+ return name.replace("_", "-")
193
+
194
+
195
+ def _add_kwarg(
196
+ parser: argparse.ArgumentParser, arg_name: str, *, required: bool
197
+ ) -> None:
198
+ """Add one named operation argument."""
199
+ if arg_name in _JSON_ARG_NAMES:
200
+ flag = "--" + _kebab(arg_name) + "-json"
201
+ else:
202
+ flag = "--" + _kebab(arg_name)
203
+ kwargs: dict[str, Any] = {"required": required, "default": None, "dest": arg_name}
204
+ if arg_name in _INT_ARG_NAMES:
205
+ kwargs["type"] = int
206
+ parser.add_argument(flag, **kwargs)
207
+
208
+
209
+ def build_parser() -> argparse.ArgumentParser:
210
+ """Build argparse parser with all 6 Data Health operations."""
211
+ parser = _ArgumentParser(
212
+ prog="pal-found-data-health",
213
+ description="Foundry Data Health CLI - 6 Data Health API v2 operations",
214
+ )
215
+ subparsers = parser.add_subparsers(dest="resource", help="Resource type")
216
+
217
+ for resource in sorted(OPERATION_BY_RESOURCE):
218
+ res_parser = subparsers.add_parser(_kebab(resource))
219
+ op_sub = res_parser.add_subparsers(dest="operation")
220
+ for operation, spec in sorted(OPERATION_BY_RESOURCE[resource].items()):
221
+ op_parser = op_sub.add_parser(
222
+ _kebab(operation), parents=[_common_parser()]
223
+ )
224
+ for arg_name in spec["positional"]:
225
+ op_parser.add_argument(arg_name)
226
+ for arg_name in spec["required"]:
227
+ _add_kwarg(op_parser, arg_name, required=True)
228
+ for arg_name in spec["optional"]:
229
+ _add_kwarg(op_parser, arg_name, required=False)
230
+
231
+ return parser
232
+
233
+
234
+ def _spec_for(resource: str, operation: str) -> OperationSpec:
235
+ """Return one catalog operation specification."""
236
+ try:
237
+ return OPERATION_BY_RESOURCE[resource][operation]
238
+ except KeyError as exc:
239
+ raise CLIInputError(f"Unknown operation: {resource}.{operation}") from exc
240
+
241
+
242
+ def _get_client(root_client: Any, client_path: tuple[str, ...]) -> Any:
243
+ """Resolve the exact public nested Data Health SDK resource."""
244
+ client = root_client.data_health
245
+ for attribute in client_path:
246
+ client = getattr(client, attribute)
247
+ return client
248
+
249
+
250
+ def _required_text(value: Any, *, field: str) -> str:
251
+ """Validate a non-empty string without echoing its value."""
252
+ if not isinstance(value, str) or not value.strip():
253
+ raise CLIInputError(f"{field} must not be empty")
254
+ return value
255
+
256
+
257
+ def _decode_json(value: str, *, field: str) -> Any:
258
+ try:
259
+ return json.loads(value)
260
+ except json.JSONDecodeError as exc:
261
+ raise CLIInputError(f"{field} must contain valid JSON") from exc
262
+
263
+
264
+ def _parse_json_object(value: str, *, field: str) -> dict[str, Any]:
265
+ """Decode one JSON object."""
266
+ result = _decode_json(value, field=field)
267
+ if not isinstance(result, dict):
268
+ raise CLIInputError(f"{field} must be a JSON object")
269
+ return result
270
+
271
+
272
+ def _validate_timeout(value: int) -> int:
273
+ """Validate the effective per-attempt timeout from ADR-002."""
274
+ if isinstance(value, bool) or not isinstance(value, int) or not 1 <= value <= 3600:
275
+ raise CLIInputError("timeout must be between 1 and 3600 seconds")
276
+ return value
277
+
278
+
279
+ def _validate_limit(value: int) -> int:
280
+ """Validate the ``check_report get_latest`` response bound (1..100).
281
+
282
+ The installed SDK exposes ``CheckReportLimit`` as a plain int alias
283
+ without bounds; the server would otherwise reject out-of-range values
284
+ with a 400. Validating locally gives an earlier, privacy-safe error
285
+ (ADR-001 exit 1) before any client is created.
286
+ """
287
+ if isinstance(value, bool) or not isinstance(value, int) or not 1 <= value <= 100:
288
+ raise CLIInputError("limit must be between 1 and 100")
289
+ return value
290
+
291
+
292
+ def _validate_inputs(spec: OperationSpec, args: argparse.Namespace) -> None:
293
+ """Validate scalars and decode JSON arguments before client creation."""
294
+ for name in (*spec["positional"], *spec["required"]):
295
+ value = getattr(args, name, None)
296
+ if isinstance(value, str):
297
+ _required_text(value, field=name)
298
+ for name in spec["json_args"]:
299
+ value = getattr(args, name, None)
300
+ if value is not None:
301
+ setattr(args, name, _parse_json_object(value, field=name))
302
+ for name in spec["optional"]:
303
+ if name not in _INT_ARG_NAMES or name != "limit":
304
+ continue
305
+ value = getattr(args, name, None)
306
+ if value is not None:
307
+ setattr(args, name, _validate_limit(value))
308
+
309
+
310
+ def _model_to_dict(value: Any) -> Any:
311
+ """Convert SDK models to JSON-compatible values."""
312
+ if value is None:
313
+ return None
314
+ if isinstance(value, list):
315
+ return [_model_to_dict(item) for item in value]
316
+ if isinstance(value, dict):
317
+ return {key: _model_to_dict(item) for key, item in value.items()}
318
+ for method_name in ("to_dict", "model_dump", "dict"):
319
+ method = getattr(value, method_name, None)
320
+ if callable(method):
321
+ result = method()
322
+ if isinstance(result, dict):
323
+ return {key: _model_to_dict(item) for key, item in result.items()}
324
+ return value
325
+
326
+
327
+ def _build_kwargs(
328
+ spec: OperationSpec, args: argparse.Namespace, timeout: int
329
+ ) -> dict[str, Any]:
330
+ """Build SDK keyword arguments, omitting absent optional values."""
331
+ kwargs: dict[str, Any] = {}
332
+ for name in (*spec["required"], *spec["optional"]):
333
+ value = getattr(args, name, None)
334
+ if value is not None:
335
+ kwargs[name] = value
336
+ kwargs["request_timeout"] = timeout
337
+ return kwargs
338
+
339
+
340
+ async def _invoke(
341
+ spec: OperationSpec,
342
+ client: Any,
343
+ args: argparse.Namespace,
344
+ timeout: int,
345
+ ) -> Any:
346
+ """Invoke one SDK operation."""
347
+ positional = [getattr(args, name) for name in spec["positional"]]
348
+ return await getattr(client, spec["method"])(
349
+ *positional, **_build_kwargs(spec, args, timeout)
350
+ )
351
+
352
+
353
+ def _serialize_error(exception: BaseException) -> int:
354
+ """Write one privacy-safe ADR-001 error envelope to stdout."""
355
+ serializer = ErrorSerializer()
356
+ status = sdk_http_status(exception)
357
+ declared = getattr(exception, "exit_code", None)
358
+ sdk_exit = sdk_exception_exit_code(exception)
359
+ if isinstance(declared, int) and 1 <= declared <= 9:
360
+ exit_code = declared
361
+ elif sdk_exit is not None:
362
+ exit_code = sdk_exit
363
+ elif status == 401:
364
+ exit_code = EXIT_AUTH
365
+ elif status == 403:
366
+ exit_code = EXIT_PERMISSION_DENIED
367
+ elif status == 404:
368
+ exit_code = EXIT_NOT_FOUND
369
+ elif status == 429:
370
+ exit_code = EXIT_RATE_LIMIT
371
+ elif isinstance(exception, ConfigurationError):
372
+ exit_code = EXIT_CONFIGURATION
373
+ elif isinstance(exception, (TypeError, ValueError)):
374
+ exit_code = EXIT_USER_INPUT
375
+ elif isinstance(exception, TimeoutError):
376
+ exit_code = EXIT_TIMEOUT
377
+ else:
378
+ exit_code = EXIT_SERVER_ERROR
379
+
380
+ safe = isinstance(
381
+ exception,
382
+ (CLIInputError, ConfigurationError, TimeoutError),
383
+ ) or declared in {EXIT_ACCESS_CONTROL, EXIT_CONFIGURATION}
384
+ message = str(exception) if safe else "DataHealth operation failed"
385
+ envelope = serializer.create_error_envelope(
386
+ exit_code, message, type(exception).__name__, serializer.call_id
387
+ )
388
+ if isinstance(status, int):
389
+ envelope["http_status"] = status
390
+ sys.stdout.write(json.dumps(envelope, default=str) + "\n")
391
+ sys.stdout.flush()
392
+ logger.error(
393
+ "DataHealth operation failed",
394
+ extra={
395
+ "call_id": serializer.call_id,
396
+ "exception_type": type(exception).__name__,
397
+ "http_status": status,
398
+ },
399
+ )
400
+ return exit_code
401
+
402
+
403
+ async def main() -> int:
404
+ """Run one stateless Foundry Data Health invocation."""
405
+ parser = build_parser()
406
+ try:
407
+ args = parser.parse_args()
408
+ if not args.resource or not getattr(args, "operation", None):
409
+ raise CLIInputError("a DataHealth operation is required")
410
+ except CLIInputError as exc:
411
+ return _serialize_error(exc)
412
+
413
+ try:
414
+ cfg = ConfigLoader()
415
+ cfg.load()
416
+ LogSetup.configure(log_level=cfg.log_level)
417
+
418
+ resource = args.resource.replace("-", "_")
419
+ operation = args.operation.replace("-", "_")
420
+ spec = _spec_for(resource, operation)
421
+ _validate_inputs(spec, args)
422
+ timeout = _validate_timeout(
423
+ args.timeout if args.timeout is not None else cfg.timeout_s
424
+ )
425
+ AccessControlGuard(
426
+ cfg,
427
+ "DATA_HEALTH",
428
+ metadata_allowlist_path=str(_METADATA_ALLOWLIST_PATH),
429
+ ).check(resource, operation)
430
+
431
+ factory = AsyncClientFactory()
432
+ with factory.invocation_scope(cfg, include_attribution=False):
433
+ root_client = factory.create(cfg, include_attribution=False)
434
+ client = _get_client(root_client, spec["client_path"])
435
+ retry_handler = RetryHandler(timeout_s=timeout)
436
+ result = await retry_handler.execute(
437
+ _invoke, spec, client, args, timeout
438
+ )
439
+
440
+ formatter = OutputFormatter(format_setting=args.format, pretty=args.pretty)
441
+ print(formatter.format(_model_to_dict(result)))
442
+ return EXIT_SUCCESS
443
+ except AccessControlError as exc:
444
+ ErrorSerializer().serialize(exc)
445
+ return EXIT_ACCESS_CONTROL
446
+ except asyncio.CancelledError:
447
+ return _serialize_error(TimeoutError("Operation cancelled"))
448
+ except Exception as exc:
449
+ return _serialize_error(exc)
450
+
451
+
452
+ def console_main() -> int:
453
+ """Run the async CLI through one event-loop boundary."""
454
+ return asyncio.run(main())
455
+
456
+
457
+ if __name__ == "__main__":
458
+ raise SystemExit(console_main())
@@ -0,0 +1 @@
1
+ """Foundry Datasets CLI package."""
@@ -0,0 +1 @@
1
+ """Console entry point modules for Foundry Datasets."""