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,388 @@
1
+ """ConfigLoader — .env file search path (ADR-006).
2
+
3
+ Loads configuration from .env file following ADR-006 search path order:
4
+ 1. Explicit path via FOUNDRY_AGENTIC_CLI_ENV_FILE
5
+ 2. Git root .env (walk up from CWD to find .git)
6
+ 3. CWD .env fallback (non-git deployment)
7
+ 4. Environment variables only (no .env file)
8
+
9
+ Never searches home directory.
10
+ """
11
+
12
+ import os
13
+ from enum import Enum
14
+ from pathlib import Path
15
+ from typing import TypeVar
16
+
17
+ from dotenv import load_dotenv
18
+
19
+ from pal_found_cli.common.log_setup import DEFAULT_LOG_LEVEL, ENV_LOG_LEVEL
20
+
21
+ # Environment variable names
22
+ ENV_ENV_FILE = "FOUNDRY_AGENTIC_CLI_ENV_FILE"
23
+ ENV_TOKEN = "FOUNDRY_TOKEN" # nosec B105 - environment variable name, not a secret value.
24
+ ENV_HOSTNAME = "FOUNDRY_HOSTNAME"
25
+ ENV_TIMEOUT_S = "FOUNDRY_AGENTIC_CLI_TIMEOUT_S"
26
+ ENV_DEFAULT_FORMAT = "FOUNDRY_AGENTIC_CLI_DEFAULT_FORMAT"
27
+ ENV_ENABLED = "FOUNDRY_AGENTIC_CLI_ENABLED"
28
+ ENV_READONLY = "FOUNDRY_AGENTIC_CLI_READONLY"
29
+ ENV_METADATA_ONLY = "FOUNDRY_AGENTIC_CLI_METADATA_ONLY"
30
+ ENV_ENABLE_ATTRIBUTION = "FOUNDRY_AGENTIC_CLI_ENABLE_ATTRIBUTION"
31
+ ENV_ATTRIBUTION_RIDS = "FOUNDRY_AGENTIC_CLI_ATTRIBUTION_RIDS"
32
+ ENV_ENABLE_TRACING = "FOUNDRY_AGENTIC_CLI_ENABLE_TRACING"
33
+ ENV_MAX_DOWNLOAD_BYTES = "FOUNDRY_AGENTIC_CLI_MAX_DOWNLOAD_BYTES"
34
+ ENV_DOWNLOAD_PATH = "FOUNDRY_AGENTIC_CLI_DOWNLOAD_PATH"
35
+ ENV_SESSION_PATH = "FOUNDRY_AGENTIC_CLI_SESSION_PATH"
36
+
37
+ # Defaults
38
+ DEFAULT_TIMEOUT_S = 30
39
+ DEFAULT_MIN_TIMEOUT_S = 1
40
+ DEFAULT_MAX_TIMEOUT_S = 3600
41
+ DEFAULT_FORMAT = "auto"
42
+ DEFAULT_MAX_DOWNLOAD_BYTES = 1_572_864
43
+ DEFAULT_DOWNLOAD_PATH = Path(".foundry-data/downloads")
44
+ DEFAULT_SESSION_PATH = Path(".foundry-data/sessions")
45
+
46
+ # Exit code per ADR-001
47
+ EXIT_CONFIGURATION = 9
48
+
49
+ # Truthy boolean string set — kept consistent across property accessors
50
+ # and get_bool() so the parsing contract is identical everywhere.
51
+ _TRUTHY = ("true", "1", "yes", "on")
52
+
53
+ _E = TypeVar("_E", bound=Enum)
54
+
55
+
56
+ class ConfigLoader:
57
+ """Loads configuration from .env file following ADR-006 search path.
58
+
59
+ Search path order:
60
+ 1. Explicit: FOUNDRY_AGENTIC_CLI_ENV_FILE
61
+ 2. Git root: Walk up from CWD to find .git, load .env there
62
+ 3. Env vars only: No .env file, use shell environment
63
+
64
+ Usage
65
+ -----
66
+ >>> cfg = ConfigLoader()
67
+ >>> cfg.load()
68
+ >>> token = cfg.token
69
+ """
70
+
71
+ def __init__(self) -> None:
72
+ self._loaded_file: str | None = None
73
+
74
+ def load(self) -> None:
75
+ """Load configuration following ADR-006 search path."""
76
+ # Order 1 — Explicit override
77
+ explicit_path = os.environ.get(ENV_ENV_FILE)
78
+ if explicit_path:
79
+ path = Path(explicit_path)
80
+ if not path.exists():
81
+ raise ConfigurationError(
82
+ f"Explicit .env file not found: {explicit_path}"
83
+ )
84
+ load_dotenv(str(path), override=False)
85
+ self._loaded_file = str(path)
86
+ return
87
+
88
+ # Order 2 — Git root .env
89
+ git_root = self._find_git_root()
90
+ if git_root:
91
+ env_path = git_root / ".env"
92
+ if env_path.exists():
93
+ load_dotenv(str(env_path), override=False)
94
+ self._loaded_file = str(env_path)
95
+ return
96
+
97
+ # Order 3 — CWD .env fallback
98
+ cwd_env = Path.cwd() / ".env"
99
+ if cwd_env.exists():
100
+ load_dotenv(str(cwd_env), override=False)
101
+ self._loaded_file = str(cwd_env)
102
+ return
103
+
104
+ # Order 4 — Env vars only
105
+ # No error — credentials may be in shell environment
106
+
107
+ def _find_git_root(self, max_depth: int = 20) -> Path | None:
108
+ """Walk up from CWD to find .git directory.
109
+
110
+ Parameters
111
+ ----------
112
+ max_depth : int
113
+ Maximum directory levels to walk up.
114
+
115
+ Returns
116
+ -------
117
+ Path or None
118
+ Git root directory or None if not found.
119
+ """
120
+ current = Path.cwd()
121
+ depth = 0
122
+ while depth < max_depth:
123
+ if (current / ".git").exists():
124
+ return current
125
+ parent = current.parent
126
+ if parent == current:
127
+ break
128
+ current = parent
129
+ depth += 1
130
+ return None
131
+
132
+ # --- Typed accessors (generic, follow story AC) ---
133
+
134
+ def get_str(self, name: str, default: str | None = None) -> str | None:
135
+ """Get string environment variable value.
136
+
137
+ Parameters
138
+ ----------
139
+ name : str
140
+ Environment variable name.
141
+ default : str, optional
142
+ Default value if not set.
143
+
144
+ Returns
145
+ -------
146
+ str or None
147
+ Environment variable value, stripped of surrounding whitespace,
148
+ or ``default`` if unset.
149
+ """
150
+ val = os.environ.get(name)
151
+ if val is None:
152
+ return default
153
+ return val.strip()
154
+
155
+ def get_bool(self, name: str, default: bool = False) -> bool:
156
+ """Get boolean environment variable value.
157
+
158
+ Truthy values (case-insensitive): ``true``, ``1``, ``yes``, ``on``.
159
+ Everything else (including unset) evaluates per ``default``.
160
+
161
+ Parameters
162
+ ----------
163
+ name : str
164
+ Environment variable name.
165
+ default : bool
166
+ Default value when the variable is unset.
167
+
168
+ Returns
169
+ -------
170
+ bool
171
+ Parsed boolean value.
172
+ """
173
+ val = os.environ.get(name)
174
+ if val is None:
175
+ return default
176
+ return val.strip().lower() in _TRUTHY
177
+
178
+ def get_int(self, name: str, default: int | None = None) -> int | None:
179
+ """Get integer environment variable value.
180
+
181
+ Parameters
182
+ ----------
183
+ name : str
184
+ Environment variable name.
185
+ default : int, optional
186
+ Default value when unset or non-numeric.
187
+
188
+ Returns
189
+ -------
190
+ int or None
191
+ Parsed integer, or ``default`` when unset / unparsable.
192
+ """
193
+ val = os.environ.get(name)
194
+ if val is None or val.strip() == "":
195
+ return default
196
+ try:
197
+ return int(val.strip())
198
+ except ValueError:
199
+ return default
200
+
201
+ def get_float(self, name: str, default: float | None = None) -> float | None:
202
+ """Get float environment variable value.
203
+
204
+ Parameters
205
+ ----------
206
+ name : str
207
+ Environment variable name.
208
+ default : float, optional
209
+ Default value when unset or non-numeric.
210
+
211
+ Returns
212
+ -------
213
+ float or None
214
+ Parsed float, or ``default`` when unset / unparsable.
215
+ """
216
+ val = os.environ.get(name)
217
+ if val is None or val.strip() == "":
218
+ return default
219
+ try:
220
+ return float(val.strip())
221
+ except ValueError:
222
+ return default
223
+
224
+ def get_enum(
225
+ self, name: str, enum_cls: type[_E], default: _E | None = None
226
+ ) -> _E | None:
227
+ """Get enum environment variable value (case-insensitive).
228
+
229
+ Parameters
230
+ ----------
231
+ name : str
232
+ Environment variable name.
233
+ enum_cls : Type[Enum]
234
+ Enum class to coerce into.
235
+ default : Enum member, optional
236
+ Default value when unset or not a valid member.
237
+
238
+ Returns
239
+ -------
240
+ Enum member or None
241
+ Matching enum member, or ``default``.
242
+ """
243
+ val = os.environ.get(name)
244
+ if val is None or val.strip() == "":
245
+ return default
246
+ key = val.strip()
247
+ # Try name match first (case-insensitive)
248
+ for member in enum_cls:
249
+ if member.name.lower() == key.lower():
250
+ return member
251
+ # Fall back to value match (case-sensitive — values may be meaningful)
252
+ for member in enum_cls:
253
+ if member.value == key:
254
+ return member
255
+ return default
256
+
257
+ # --- Legacy helper aliases (kept for backward compatibility) ---
258
+
259
+ def get_env(self, name: str, default: str | None = None) -> str | None:
260
+ """Get raw environment variable value (alias of :meth:`get_str`)."""
261
+ return self.get_str(name, default)
262
+
263
+ def get_env_bool(self, name: str, default: bool = False) -> bool:
264
+ """Get boolean environment variable value (alias of :meth:`get_bool`)."""
265
+ return self.get_bool(name, default)
266
+
267
+ # --- Property accessors ---
268
+
269
+ @property
270
+ def token(self) -> str | None:
271
+ """FOUNDRY_TOKEN value."""
272
+ return os.environ.get(ENV_TOKEN)
273
+
274
+ @property
275
+ def hostname(self) -> str | None:
276
+ """FOUNDRY_HOSTNAME value."""
277
+ return os.environ.get(ENV_HOSTNAME)
278
+
279
+ @property
280
+ def timeout_s(self) -> int:
281
+ """Call timeout in seconds (ADR-002)."""
282
+ val = os.environ.get(ENV_TIMEOUT_S)
283
+ if val is None:
284
+ return DEFAULT_TIMEOUT_S
285
+ try:
286
+ timeout = int(val)
287
+ return max(DEFAULT_MIN_TIMEOUT_S, min(DEFAULT_MAX_TIMEOUT_S, timeout))
288
+ except ValueError:
289
+ return DEFAULT_TIMEOUT_S
290
+
291
+ @property
292
+ def default_format(self) -> str:
293
+ """Default output format."""
294
+ return os.environ.get(ENV_DEFAULT_FORMAT, DEFAULT_FORMAT)
295
+
296
+ @property
297
+ def log_level(self) -> str:
298
+ """Log level (ADR-005)."""
299
+ return os.environ.get(ENV_LOG_LEVEL, DEFAULT_LOG_LEVEL)
300
+
301
+ @property
302
+ def global_readonly(self) -> bool:
303
+ """Global READONLY flag."""
304
+ return os.environ.get(ENV_READONLY, "").lower() in _TRUTHY
305
+
306
+ @property
307
+ def global_enabled(self) -> bool:
308
+ """Global CLI enablement flag, enabled unless explicitly false."""
309
+ return os.environ.get(ENV_ENABLED, "true").lower() not in (
310
+ "false",
311
+ "0",
312
+ "no",
313
+ "off",
314
+ )
315
+
316
+ @property
317
+ def global_metadata_only(self) -> bool:
318
+ """Global METADATA_ONLY flag."""
319
+ return os.environ.get(ENV_METADATA_ONLY, "").lower() in _TRUTHY
320
+
321
+ @property
322
+ def enable_attribution(self) -> bool:
323
+ """Attribution enabled flag."""
324
+ return os.environ.get(ENV_ENABLE_ATTRIBUTION, "").lower() in _TRUTHY
325
+
326
+ @property
327
+ def attribution_rids(self) -> str | None:
328
+ """Comma-separated attribution RIDs."""
329
+ return os.environ.get(ENV_ATTRIBUTION_RIDS)
330
+
331
+ @property
332
+ def enable_tracing(self) -> bool:
333
+ """Tracing enabled flag."""
334
+ return os.environ.get(ENV_ENABLE_TRACING, "").lower() in _TRUTHY
335
+
336
+ @property
337
+ def max_download_bytes(self) -> int:
338
+ """Maximum persisted binary response size in bytes."""
339
+ raw = os.environ.get(ENV_MAX_DOWNLOAD_BYTES)
340
+ if raw is None:
341
+ return DEFAULT_MAX_DOWNLOAD_BYTES
342
+ try:
343
+ value = int(raw.strip())
344
+ except ValueError as exc:
345
+ raise ConfigurationError(
346
+ f"{ENV_MAX_DOWNLOAD_BYTES} must be a positive integer"
347
+ ) from exc
348
+ if value <= 0:
349
+ raise ConfigurationError(
350
+ f"{ENV_MAX_DOWNLOAD_BYTES} must be a positive integer"
351
+ )
352
+ return value
353
+
354
+ @property
355
+ def download_path(self) -> Path:
356
+ """Validated base path for binary downloads."""
357
+ return self._configured_path(ENV_DOWNLOAD_PATH, DEFAULT_DOWNLOAD_PATH)
358
+
359
+ @property
360
+ def session_path(self) -> Path:
361
+ """Validated base path for persisted sessions."""
362
+ return self._configured_path(ENV_SESSION_PATH, DEFAULT_SESSION_PATH)
363
+
364
+ @staticmethod
365
+ def _configured_path(name: str, default: Path) -> Path:
366
+ raw = os.environ.get(name)
367
+ if raw is None:
368
+ return default
369
+ value = raw.strip()
370
+ if not value or "\x00" in value:
371
+ raise ConfigurationError(f"{name} must be a non-empty filesystem path")
372
+ return Path(value)
373
+
374
+ @property
375
+ def loaded_file(self) -> str | None:
376
+ """Path to loaded .env file, or None."""
377
+ return self._loaded_file
378
+
379
+
380
+ class ConfigurationError(Exception):
381
+ """Configuration loading error (exit code 9 per ADR-001).
382
+
383
+ Carries the canonical exit code so callers (CLI entrypoints, error
384
+ serializers) can map the exception to the correct process exit status
385
+ without maintaining a separate lookup table.
386
+ """
387
+
388
+ exit_code: int = EXIT_CONFIGURATION