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.
- pal_found_cli/__init__.py +1 -0
- pal_found_cli/_version.py +24 -0
- pal_found_cli/admin/__init__.py +2 -0
- pal_found_cli/admin/scripts/__init__.py +2 -0
- pal_found_cli/admin/scripts/pal_found_admin_cli.py +600 -0
- pal_found_cli/aip_agents/__init__.py +1 -0
- pal_found_cli/aip_agents/metadata-allow-list.md +20 -0
- pal_found_cli/aip_agents/scripts/__init__.py +1 -0
- pal_found_cli/aip_agents/scripts/pal_found_aip_agents_cli.py +704 -0
- pal_found_cli/audit/__init__.py +1 -0
- pal_found_cli/audit/metadata-allow-list.md +4 -0
- pal_found_cli/audit/scripts/__init__.py +1 -0
- pal_found_cli/audit/scripts/pal_found_audit_cli.py +387 -0
- pal_found_cli/checkpoints/__init__.py +9 -0
- pal_found_cli/checkpoints/metadata-allow-list.md +7 -0
- pal_found_cli/checkpoints/scripts/__init__.py +1 -0
- pal_found_cli/checkpoints/scripts/pal_found_checkpoints_cli.py +527 -0
- pal_found_cli/common/__init__.py +56 -0
- pal_found_cli/common/access_control_guard.py +591 -0
- pal_found_cli/common/async_client_factory.py +161 -0
- pal_found_cli/common/auth_provider.py +82 -0
- pal_found_cli/common/binary_download_handler.py +288 -0
- pal_found_cli/common/config_loader.py +388 -0
- pal_found_cli/common/error_serializer.py +412 -0
- pal_found_cli/common/log_setup.py +241 -0
- pal_found_cli/common/metadata-allow-list.md +169 -0
- pal_found_cli/common/output_formatter.py +267 -0
- pal_found_cli/common/pagination_helper.py +212 -0
- pal_found_cli/common/retry.py +617 -0
- pal_found_cli/common/sdk_error_utils.py +112 -0
- pal_found_cli/common/session_manager.py +461 -0
- pal_found_cli/common/tracing_provider.py +107 -0
- pal_found_cli/connectivity/__init__.py +9 -0
- pal_found_cli/connectivity/metadata-allow-list.md +24 -0
- pal_found_cli/connectivity/scripts/__init__.py +1 -0
- pal_found_cli/connectivity/scripts/pal_found_connectivity_cli.py +720 -0
- pal_found_cli/data_health/__init__.py +9 -0
- pal_found_cli/data_health/metadata-allow-list.md +10 -0
- pal_found_cli/data_health/scripts/__init__.py +1 -0
- pal_found_cli/data_health/scripts/pal_found_data_health_cli.py +458 -0
- pal_found_cli/datasets/__init__.py +1 -0
- pal_found_cli/datasets/scripts/__init__.py +1 -0
- pal_found_cli/datasets/scripts/pal_found_datasets_cli.py +732 -0
- pal_found_cli/filesystem/__init__.py +2 -0
- pal_found_cli/filesystem/scripts/__init__.py +2 -0
- pal_found_cli/filesystem/scripts/pal_found_filesystem_cli.py +659 -0
- pal_found_cli/functions/__init__.py +2 -0
- pal_found_cli/functions/scripts/__init__.py +2 -0
- pal_found_cli/functions/scripts/pal_found_functions_cli.py +389 -0
- pal_found_cli/language_models/__init__.py +1 -0
- pal_found_cli/language_models/metadata-allow-list.md +6 -0
- pal_found_cli/language_models/scripts/__init__.py +1 -0
- pal_found_cli/language_models/scripts/pal_found_language_models_cli.py +342 -0
- pal_found_cli/media_sets/__init__.py +9 -0
- pal_found_cli/media_sets/metadata-allow-list.md +23 -0
- pal_found_cli/media_sets/scripts/__init__.py +1 -0
- pal_found_cli/media_sets/scripts/pal_found_media_sets_cli.py +642 -0
- pal_found_cli/models/__init__.py +5 -0
- pal_found_cli/models/metadata-allow-list.md +27 -0
- pal_found_cli/models/scripts/__init__.py +1 -0
- pal_found_cli/models/scripts/pal_found_models_cli.py +714 -0
- pal_found_cli/ontologies/__init__.py +1 -0
- pal_found_cli/ontologies/scripts/__init__.py +1 -0
- pal_found_cli/ontologies/scripts/pal_found_ontologies_cli.py +1225 -0
- pal_found_cli/orchestration/__init__.py +9 -0
- pal_found_cli/orchestration/metadata-allow-list.md +24 -0
- pal_found_cli/orchestration/scripts/__init__.py +1 -0
- pal_found_cli/orchestration/scripts/pal_found_orchestration_cli.py +640 -0
- pal_found_cli/sql_queries/__init__.py +9 -0
- pal_found_cli/sql_queries/metadata-allow-list.md +9 -0
- pal_found_cli/sql_queries/scripts/__init__.py +1 -0
- pal_found_cli/sql_queries/scripts/pal_found_sql_queries_cli.py +514 -0
- pal_found_cli/streams/__init__.py +9 -0
- pal_found_cli/streams/metadata-allow-list.md +19 -0
- pal_found_cli/streams/scripts/__init__.py +1 -0
- pal_found_cli/streams/scripts/pal_found_streams_cli.py +615 -0
- pal_found_cli/third_party_applications/__init__.py +9 -0
- pal_found_cli/third_party_applications/metadata-allow-list.md +13 -0
- pal_found_cli/third_party_applications/scripts/__init__.py +1 -0
- pal_found_cli/third_party_applications/scripts/pal_found_third_party_applications_cli.py +547 -0
- pal_found_cli/widgets/__init__.py +9 -0
- pal_found_cli/widgets/metadata-allow-list.md +12 -0
- pal_found_cli/widgets/scripts/__init__.py +1 -0
- pal_found_cli/widgets/scripts/pal_found_widgets_cli.py +571 -0
- pal_found_cli-0.1.2.dist-info/METADATA +74 -0
- pal_found_cli-0.1.2.dist-info/RECORD +89 -0
- pal_found_cli-0.1.2.dist-info/WHEEL +5 -0
- pal_found_cli-0.1.2.dist-info/entry_points.txt +19 -0
- pal_found_cli-0.1.2.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
# Core metadata-only allow-list
|
|
2
|
+
|
|
3
|
+
This packaged policy covers namespaces that share `AccessControlGuard`'s
|
|
4
|
+
default allow-list. Namespace packages with their own policy file pass that
|
|
5
|
+
path explicitly.
|
|
6
|
+
|
|
7
|
+
| SDK operation | Decision | Notes |
|
|
8
|
+
| --- | --- | --- |
|
|
9
|
+
| `admin.authentication_provider.get` | PERMITTED | |
|
|
10
|
+
| `admin.authentication_provider.list` | PERMITTED | |
|
|
11
|
+
| `admin.cbac_banner.get` | PERMITTED | |
|
|
12
|
+
| `admin.cbac_marking_restrictions.get` | PERMITTED | |
|
|
13
|
+
| `admin.enrollment.get` | PERMITTED | |
|
|
14
|
+
| `admin.enrollment.get_current` | PERMITTED | |
|
|
15
|
+
| `admin.enrollment_role_assignment.list` | PERMITTED | |
|
|
16
|
+
| `admin.group.get` | PERMITTED | |
|
|
17
|
+
| `admin.group.get_batch` | PERMITTED | |
|
|
18
|
+
| `admin.group.list` | PERMITTED | |
|
|
19
|
+
| `admin.group.list_current` | PERMITTED | |
|
|
20
|
+
| `admin.group.search` | PERMITTED | |
|
|
21
|
+
| `admin.group_member.list` | PERMITTED | |
|
|
22
|
+
| `admin.group_membership.list` | PERMITTED | |
|
|
23
|
+
| `admin.group_membership_expiration_policy.get` | PERMITTED | |
|
|
24
|
+
| `admin.group_provider_info.get` | PERMITTED | |
|
|
25
|
+
| `admin.host.list` | PERMITTED | |
|
|
26
|
+
| `admin.marking.get` | PERMITTED | |
|
|
27
|
+
| `admin.marking.get_batch` | PERMITTED | |
|
|
28
|
+
| `admin.marking.list` | PERMITTED | |
|
|
29
|
+
| `admin.marking_category.get` | PERMITTED | |
|
|
30
|
+
| `admin.marking_category.list` | PERMITTED | |
|
|
31
|
+
| `admin.marking_member.list` | PERMITTED | |
|
|
32
|
+
| `admin.marking_role_assignment.list` | PERMITTED | |
|
|
33
|
+
| `admin.organization.get` | PERMITTED | |
|
|
34
|
+
| `admin.organization.list_available_roles` | PERMITTED | |
|
|
35
|
+
| `admin.organization_guest_member.list` | PERMITTED | |
|
|
36
|
+
| `admin.organization_role_assignment.list` | PERMITTED | |
|
|
37
|
+
| `admin.role.get` | PERMITTED | |
|
|
38
|
+
| `admin.role.get_batch` | PERMITTED | |
|
|
39
|
+
| `admin.user.get` | PERMITTED | |
|
|
40
|
+
| `admin.user.get_batch` | PERMITTED | |
|
|
41
|
+
| `admin.user.get_current` | PERMITTED | |
|
|
42
|
+
| `admin.user.get_markings` | PERMITTED | |
|
|
43
|
+
| `admin.user.list` | PERMITTED | |
|
|
44
|
+
| `admin.user.search` | PERMITTED | |
|
|
45
|
+
| `admin.user_provider_info.get` | PERMITTED | |
|
|
46
|
+
| `datasets.branch.get` | PERMITTED | |
|
|
47
|
+
| `datasets.branch.list` | PERMITTED | |
|
|
48
|
+
| `datasets.branch.transactions` | PERMITTED | |
|
|
49
|
+
| `datasets.dataset.get` | PERMITTED | |
|
|
50
|
+
| `datasets.dataset.get_health_check_reports` | PERMITTED | |
|
|
51
|
+
| `datasets.dataset.get_health_checks` | PERMITTED | |
|
|
52
|
+
| `datasets.dataset.get_schedules` | PERMITTED | |
|
|
53
|
+
| `datasets.dataset.get_schema` | PERMITTED | |
|
|
54
|
+
| `datasets.dataset.get_schema_batch` | PERMITTED | |
|
|
55
|
+
| `datasets.dataset.jobs` | PERMITTED | |
|
|
56
|
+
| `datasets.dataset.transactions` | PERMITTED | |
|
|
57
|
+
| `datasets.file.get` | PERMITTED | |
|
|
58
|
+
| `datasets.file.list` | PERMITTED | |
|
|
59
|
+
| `datasets.transaction.get` | PERMITTED | |
|
|
60
|
+
| `datasets.transaction.job` | PERMITTED | |
|
|
61
|
+
| `datasets.view.get` | PERMITTED | |
|
|
62
|
+
| `filesystem.folder.children` | PERMITTED | |
|
|
63
|
+
| `filesystem.folder.get` | PERMITTED | |
|
|
64
|
+
| `filesystem.folder.get_batch` | PERMITTED | |
|
|
65
|
+
| `filesystem.project.get` | PERMITTED | |
|
|
66
|
+
| `filesystem.project.organizations` | PERMITTED | |
|
|
67
|
+
| `filesystem.resource.get` | PERMITTED | |
|
|
68
|
+
| `filesystem.resource.get_access_requirements` | PERMITTED | |
|
|
69
|
+
| `filesystem.resource.get_batch` | PERMITTED | |
|
|
70
|
+
| `filesystem.resource.get_by_path` | PERMITTED | |
|
|
71
|
+
| `filesystem.resource.get_by_path_batch` | PERMITTED | |
|
|
72
|
+
| `filesystem.resource.markings` | PERMITTED | |
|
|
73
|
+
| `filesystem.resource_role.list` | PERMITTED | |
|
|
74
|
+
| `filesystem.space.get` | PERMITTED | |
|
|
75
|
+
| `filesystem.space.list` | PERMITTED | |
|
|
76
|
+
| `functions.query.get` | PERMITTED | |
|
|
77
|
+
| `functions.query.get_by_rid` | PERMITTED | |
|
|
78
|
+
| `functions.query.get_by_rid_batch` | PERMITTED | |
|
|
79
|
+
| `functions.value_type.get` | PERMITTED | |
|
|
80
|
+
| `functions.version_id.get` | PERMITTED | |
|
|
81
|
+
| `ontologies.action_type.get` | PERMITTED | |
|
|
82
|
+
| `ontologies.action_type.get_by_rid` | PERMITTED | |
|
|
83
|
+
| `ontologies.action_type.get_by_rid_batch` | PERMITTED | |
|
|
84
|
+
| `ontologies.action_type.list` | PERMITTED | |
|
|
85
|
+
| `ontologies.action_type_full_metadata.get` | PERMITTED | |
|
|
86
|
+
| `ontologies.action_type_full_metadata.list` | PERMITTED | |
|
|
87
|
+
| `ontologies.attachment.get` | PERMITTED | |
|
|
88
|
+
| `ontologies.attachment_property.get_attachment` | PERMITTED | |
|
|
89
|
+
| `ontologies.attachment_property.get_attachment_by_rid` | PERMITTED | |
|
|
90
|
+
| `ontologies.media_reference_property.get_media_metadata` | PERMITTED | |
|
|
91
|
+
| `ontologies.object_type.get` | PERMITTED | |
|
|
92
|
+
| `ontologies.object_type.get_by_rid_batch` | PERMITTED | |
|
|
93
|
+
| `ontologies.object_type.get_full_metadata` | PERMITTED | |
|
|
94
|
+
| `ontologies.object_type.get_outgoing_link_type` | PERMITTED | |
|
|
95
|
+
| `ontologies.object_type.list` | PERMITTED | |
|
|
96
|
+
| `ontologies.object_type.list_outgoing_link_types` | PERMITTED | |
|
|
97
|
+
| `ontologies.ontology.get` | PERMITTED | |
|
|
98
|
+
| `ontologies.ontology.get_full_metadata` | PERMITTED | |
|
|
99
|
+
| `ontologies.ontology.list` | PERMITTED | |
|
|
100
|
+
| `ontologies.ontology.load_metadata` | PERMITTED | |
|
|
101
|
+
| `ontologies.ontology_interface.get` | PERMITTED | |
|
|
102
|
+
| `ontologies.ontology_interface.get_outgoing_interface_link_type` | PERMITTED | |
|
|
103
|
+
| `ontologies.ontology_interface.list` | PERMITTED | |
|
|
104
|
+
| `ontologies.ontology_interface.list_outgoing_interface_link_types` | PERMITTED | |
|
|
105
|
+
| `ontologies.ontology_value_type.get` | PERMITTED | |
|
|
106
|
+
| `ontologies.ontology_value_type.list` | PERMITTED | |
|
|
107
|
+
| `ontologies.query_type.get` | PERMITTED | |
|
|
108
|
+
| `ontologies.query_type.list` | PERMITTED | |
|
|
109
|
+
| `aip_agents.agent.get` | PERMITTED | |
|
|
110
|
+
| `aip_agents.agent_version.get` | PERMITTED | |
|
|
111
|
+
| `aip_agents.agent_version.list` | PERMITTED | |
|
|
112
|
+
| `aip_agents.session.get` | PERMITTED | |
|
|
113
|
+
| `aip_agents.session.list` | PERMITTED | |
|
|
114
|
+
| `aip_agents.session_trace.get` | PERMITTED | |
|
|
115
|
+
| `audit.log_file.list` | PERMITTED | |
|
|
116
|
+
| `checkpoints.record.get` | PERMITTED | |
|
|
117
|
+
| `checkpoints.record.get_batch` | PERMITTED | |
|
|
118
|
+
| `checkpoints.record.search` | PERMITTED | |
|
|
119
|
+
| `connectivity.connection.get` | PERMITTED | |
|
|
120
|
+
| `connectivity.connection.get_configuration` | PERMITTED | |
|
|
121
|
+
| `connectivity.connection.get_configuration_batch` | PERMITTED | |
|
|
122
|
+
| `connectivity.file_import.get` | PERMITTED | |
|
|
123
|
+
| `connectivity.file_import.list` | PERMITTED | |
|
|
124
|
+
| `connectivity.table_import.get` | PERMITTED | |
|
|
125
|
+
| `connectivity.table_import.list` | PERMITTED | |
|
|
126
|
+
| `data_health.check.get` | PERMITTED | |
|
|
127
|
+
| `data_health.check_report.get` | PERMITTED | |
|
|
128
|
+
| `data_health.check_report.get_latest` | PERMITTED | |
|
|
129
|
+
| `media_sets.media_set.get` | PERMITTED | |
|
|
130
|
+
| `media_sets.media_set.get_rid_by_path` | PERMITTED | |
|
|
131
|
+
| `media_sets.media_set.get_status` | PERMITTED | |
|
|
132
|
+
| `media_sets.media_set.info` | PERMITTED | |
|
|
133
|
+
| `media_sets.media_set.metadata` | PERMITTED | |
|
|
134
|
+
| `models.experiment.get` | PERMITTED | |
|
|
135
|
+
| `models.experiment.search` | PERMITTED | |
|
|
136
|
+
| `models.model.get` | PERMITTED | |
|
|
137
|
+
| `models.model_studio.get` | PERMITTED | |
|
|
138
|
+
| `models.model_studio_config_version.get` | PERMITTED | |
|
|
139
|
+
| `models.model_studio_config_version.latest` | PERMITTED | |
|
|
140
|
+
| `models.model_studio_config_version.list` | PERMITTED | |
|
|
141
|
+
| `models.model_studio_run.list` | PERMITTED | |
|
|
142
|
+
| `models.model_studio_trainer.get` | PERMITTED | |
|
|
143
|
+
| `models.model_studio_trainer.list` | PERMITTED | |
|
|
144
|
+
| `models.model_version.get` | PERMITTED | |
|
|
145
|
+
| `models.model_version.list` | PERMITTED | |
|
|
146
|
+
| `orchestration.build.get` | PERMITTED | |
|
|
147
|
+
| `orchestration.build.get_batch` | PERMITTED | |
|
|
148
|
+
| `orchestration.build.jobs` | PERMITTED | |
|
|
149
|
+
| `orchestration.build.search` | PERMITTED | |
|
|
150
|
+
| `orchestration.job.get` | PERMITTED | |
|
|
151
|
+
| `orchestration.job.get_batch` | PERMITTED | |
|
|
152
|
+
| `orchestration.schedule.get` | PERMITTED | |
|
|
153
|
+
| `orchestration.schedule.get_affected_resources` | PERMITTED | |
|
|
154
|
+
| `orchestration.schedule.get_batch` | PERMITTED | |
|
|
155
|
+
| `orchestration.schedule.runs` | PERMITTED | |
|
|
156
|
+
| `orchestration.schedule_version.get` | PERMITTED | |
|
|
157
|
+
| `orchestration.schedule_version.schedule` | PERMITTED | |
|
|
158
|
+
| `sql_queries.sql_query.get_status` | PERMITTED | |
|
|
159
|
+
| `streams.stream.get` | PERMITTED | |
|
|
160
|
+
| `streams.stream.get_end_offsets` | PERMITTED | |
|
|
161
|
+
| `streams.subscriber.get_read_position` | PERMITTED | |
|
|
162
|
+
| `third_party_applications.third_party_application.get` | PERMITTED | |
|
|
163
|
+
| `third_party_applications.version.get` | PERMITTED | |
|
|
164
|
+
| `third_party_applications.version.list` | PERMITTED | |
|
|
165
|
+
| `third_party_applications.website.get` | PERMITTED | |
|
|
166
|
+
| `widgets.release.get` | PERMITTED | |
|
|
167
|
+
| `widgets.release.list` | PERMITTED | |
|
|
168
|
+
| `widgets.repository.get` | PERMITTED | |
|
|
169
|
+
| `widgets.widget_set.get` | PERMITTED | |
|
|
@@ -0,0 +1,267 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""OutputFormatter with auto-selection (ADR-004).
|
|
3
|
+
|
|
4
|
+
Implements the format auto-selection algorithm defined in ADR-004.
|
|
5
|
+
Supports JSON and TOON output formats with intelligent format selection
|
|
6
|
+
based on data shape and user configuration.
|
|
7
|
+
|
|
8
|
+
Format Selection Algorithm (ADR-004)
|
|
9
|
+
------------------------------------
|
|
10
|
+
1. Explicit format always wins
|
|
11
|
+
2. Errors always use JSON
|
|
12
|
+
3. Non-list top-level always uses JSON
|
|
13
|
+
4. Empty list uses JSON
|
|
14
|
+
5. Extract field sets from all items
|
|
15
|
+
6. Any non-dict item → JSON
|
|
16
|
+
7. All items must share identical field set → TOON, else JSON
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
import json
|
|
20
|
+
import os
|
|
21
|
+
import sys
|
|
22
|
+
from typing import Any
|
|
23
|
+
|
|
24
|
+
# Environment variable for default format
|
|
25
|
+
ENV_DEFAULT_FORMAT = "FOUNDRY_AGENTIC_CLI_DEFAULT_FORMAT"
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class OutputFormatter:
|
|
29
|
+
"""Formats output data as JSON or TOON with auto-selection (ADR-004).
|
|
30
|
+
|
|
31
|
+
Implements the 7-step auto-selection algorithm from ADR-004.
|
|
32
|
+
Supports explicit format override and pretty-printing.
|
|
33
|
+
|
|
34
|
+
Parameters
|
|
35
|
+
----------
|
|
36
|
+
format_setting : str, optional
|
|
37
|
+
Format setting: 'json', 'toon', or 'auto'. Defaults to env var
|
|
38
|
+
`FOUNDRY_AGENTIC_CLI_DEFAULT_FORMAT`, then to 'auto'.
|
|
39
|
+
pretty : bool, optional
|
|
40
|
+
Enable pretty-printing for JSON output. Default: False.
|
|
41
|
+
|
|
42
|
+
Examples
|
|
43
|
+
--------
|
|
44
|
+
>>> formatter = OutputFormatter(format_setting="auto")
|
|
45
|
+
>>> output = formatter.format(data)
|
|
46
|
+
>>> print(output)
|
|
47
|
+
"""
|
|
48
|
+
|
|
49
|
+
def __init__(
|
|
50
|
+
self,
|
|
51
|
+
format_setting: str | None = None,
|
|
52
|
+
pretty: bool = False,
|
|
53
|
+
) -> None:
|
|
54
|
+
self.format_setting = format_setting or os.environ.get(
|
|
55
|
+
ENV_DEFAULT_FORMAT, "auto"
|
|
56
|
+
)
|
|
57
|
+
self.pretty = pretty
|
|
58
|
+
|
|
59
|
+
def _select_format(self, data: Any) -> str:
|
|
60
|
+
"""Apply ADR-004 format auto-selection algorithm.
|
|
61
|
+
|
|
62
|
+
Algorithm:
|
|
63
|
+
1. Explicit format always wins
|
|
64
|
+
2. Errors always use JSON
|
|
65
|
+
3. Non-list top-level always uses JSON
|
|
66
|
+
4. Empty list uses JSON
|
|
67
|
+
5. Extract field sets from all items
|
|
68
|
+
6. Any non-dict item → JSON
|
|
69
|
+
7. All items must share identical field set → TOON, else JSON
|
|
70
|
+
|
|
71
|
+
Parameters
|
|
72
|
+
----------
|
|
73
|
+
data : Any
|
|
74
|
+
The data to format.
|
|
75
|
+
|
|
76
|
+
Returns
|
|
77
|
+
-------
|
|
78
|
+
str
|
|
79
|
+
Selected format: 'json' or 'toon'.
|
|
80
|
+
"""
|
|
81
|
+
# 1. Explicit format always wins
|
|
82
|
+
if self.format_setting in ("json", "toon"):
|
|
83
|
+
return self.format_setting
|
|
84
|
+
|
|
85
|
+
# 2. Errors always use JSON
|
|
86
|
+
if isinstance(data, dict) and data.get("error"):
|
|
87
|
+
return "json"
|
|
88
|
+
|
|
89
|
+
# 3. Non-list top-level always uses JSON
|
|
90
|
+
if not isinstance(data, list):
|
|
91
|
+
return "json"
|
|
92
|
+
|
|
93
|
+
# 4. Empty list uses JSON
|
|
94
|
+
if len(data) == 0:
|
|
95
|
+
return "json"
|
|
96
|
+
|
|
97
|
+
# 5. Extract field sets from all items
|
|
98
|
+
field_sets = []
|
|
99
|
+
for item in data:
|
|
100
|
+
if isinstance(item, dict):
|
|
101
|
+
field_sets.append(frozenset(item.keys()))
|
|
102
|
+
|
|
103
|
+
# 6. Any non-dict item → JSON
|
|
104
|
+
if len(field_sets) != len(data):
|
|
105
|
+
return "json"
|
|
106
|
+
|
|
107
|
+
# 7. All items must share identical field set → TOON, else JSON
|
|
108
|
+
if len(set(field_sets)) == 1:
|
|
109
|
+
return "toon"
|
|
110
|
+
|
|
111
|
+
return "json"
|
|
112
|
+
|
|
113
|
+
def _format_json(self, data: Any) -> str:
|
|
114
|
+
"""Format data as JSON.
|
|
115
|
+
|
|
116
|
+
Parameters
|
|
117
|
+
----------
|
|
118
|
+
data : Any
|
|
119
|
+
Data to serialize as JSON.
|
|
120
|
+
|
|
121
|
+
Returns
|
|
122
|
+
-------
|
|
123
|
+
str
|
|
124
|
+
JSON string.
|
|
125
|
+
"""
|
|
126
|
+
if self.pretty:
|
|
127
|
+
return json.dumps(data, indent=2, default=str)
|
|
128
|
+
return json.dumps(data, default=str)
|
|
129
|
+
|
|
130
|
+
def _format_toon(self, data: list[dict[str, Any]]) -> str:
|
|
131
|
+
"""Format data as TOON (tabular output).
|
|
132
|
+
|
|
133
|
+
TOON format: human-readable table/line format.
|
|
134
|
+
Each dict item becomes a row with aligned columns.
|
|
135
|
+
|
|
136
|
+
Parameters
|
|
137
|
+
----------
|
|
138
|
+
data : List[Dict[str, Any]]
|
|
139
|
+
List of uniform dicts to format as table.
|
|
140
|
+
|
|
141
|
+
Returns
|
|
142
|
+
-------
|
|
143
|
+
str
|
|
144
|
+
TOON-formatted string.
|
|
145
|
+
"""
|
|
146
|
+
if not data:
|
|
147
|
+
return ""
|
|
148
|
+
|
|
149
|
+
# Extract headers (keys from first item)
|
|
150
|
+
headers = list(data[0].keys())
|
|
151
|
+
|
|
152
|
+
# Calculate column widths
|
|
153
|
+
col_widths = {h: len(str(h)) for h in headers}
|
|
154
|
+
for row in data:
|
|
155
|
+
for h in headers:
|
|
156
|
+
val_len = len(str(row.get(h, "")))
|
|
157
|
+
col_widths[h] = max(col_widths[h], val_len)
|
|
158
|
+
|
|
159
|
+
# Build table
|
|
160
|
+
lines = []
|
|
161
|
+
|
|
162
|
+
# Header line
|
|
163
|
+
header_line = " ".join(str(h).ljust(col_widths[h]) for h in headers)
|
|
164
|
+
lines.append(header_line)
|
|
165
|
+
|
|
166
|
+
# Separator
|
|
167
|
+
separator = " ".join("-" * col_widths[h] for h in headers)
|
|
168
|
+
lines.append(separator)
|
|
169
|
+
|
|
170
|
+
# Data rows
|
|
171
|
+
for row in data:
|
|
172
|
+
row_line = " ".join(
|
|
173
|
+
str(row.get(h, "")).ljust(col_widths[h]) for h in headers
|
|
174
|
+
)
|
|
175
|
+
lines.append(row_line)
|
|
176
|
+
|
|
177
|
+
return "\n".join(lines)
|
|
178
|
+
|
|
179
|
+
def format(self, data: Any) -> str:
|
|
180
|
+
"""Format data according to format setting and ADR-004 algorithm.
|
|
181
|
+
|
|
182
|
+
Parameters
|
|
183
|
+
----------
|
|
184
|
+
data : Any
|
|
185
|
+
Data to format. Can be dict, list, or scalar.
|
|
186
|
+
|
|
187
|
+
Returns
|
|
188
|
+
-------
|
|
189
|
+
str
|
|
190
|
+
Formatted string output.
|
|
191
|
+
|
|
192
|
+
Raises
|
|
193
|
+
------
|
|
194
|
+
ValueError
|
|
195
|
+
If format_setting is not one of 'json', 'toon', 'auto'.
|
|
196
|
+
"""
|
|
197
|
+
if self.format_setting not in ("json", "toon", "auto"):
|
|
198
|
+
raise ValueError(
|
|
199
|
+
f"Invalid format_setting '{self.format_setting}'. "
|
|
200
|
+
f"Must be one of: 'json', 'toon', 'auto'"
|
|
201
|
+
)
|
|
202
|
+
|
|
203
|
+
selected_format = self._select_format(data)
|
|
204
|
+
|
|
205
|
+
if selected_format == "toon":
|
|
206
|
+
if isinstance(data, list) and data and isinstance(data[0], dict):
|
|
207
|
+
return self._format_toon(data)
|
|
208
|
+
# Fallback to JSON if TOON not applicable
|
|
209
|
+
return self._format_json(data)
|
|
210
|
+
|
|
211
|
+
return self._format_json(data)
|
|
212
|
+
|
|
213
|
+
def format_error(self, error_data: dict[str, Any]) -> str:
|
|
214
|
+
"""Format error data (always JSON per ADR-004).
|
|
215
|
+
|
|
216
|
+
Parameters
|
|
217
|
+
----------
|
|
218
|
+
error_data : Dict[str, Any]
|
|
219
|
+
Error envelope dictionary.
|
|
220
|
+
|
|
221
|
+
Returns
|
|
222
|
+
-------
|
|
223
|
+
str
|
|
224
|
+
JSON-formatted error string.
|
|
225
|
+
"""
|
|
226
|
+
return self._format_json(error_data)
|
|
227
|
+
|
|
228
|
+
def emit(self, data: Any) -> None:
|
|
229
|
+
"""Format and emit data to stdout.
|
|
230
|
+
|
|
231
|
+
Parameters
|
|
232
|
+
----------
|
|
233
|
+
data : Any
|
|
234
|
+
Data to format and emit.
|
|
235
|
+
"""
|
|
236
|
+
output = self.format(data)
|
|
237
|
+
sys.stdout.write(output + "\n")
|
|
238
|
+
sys.stdout.flush()
|
|
239
|
+
|
|
240
|
+
def emit_error(self, error_data: dict[str, Any]) -> None:
|
|
241
|
+
"""Format and emit error data to stderr.
|
|
242
|
+
|
|
243
|
+
Per ADR-004, error output always goes to stderr (not stdout),
|
|
244
|
+
keeping the CLI contract of stdout for primary result data only.
|
|
245
|
+
|
|
246
|
+
Parameters
|
|
247
|
+
----------
|
|
248
|
+
error_data : Dict[str, Any]
|
|
249
|
+
Error envelope dictionary.
|
|
250
|
+
"""
|
|
251
|
+
output = self.format_error(error_data)
|
|
252
|
+
sys.stderr.write(output + "\n")
|
|
253
|
+
sys.stderr.flush()
|
|
254
|
+
|
|
255
|
+
def emit_to_stderr(self, data: Any) -> None:
|
|
256
|
+
"""Emit data (e.g. pagination metadata) to stderr as JSON.
|
|
257
|
+
|
|
258
|
+
Per ADR-004, pagination metadata always goes to stderr as JSON.
|
|
259
|
+
|
|
260
|
+
Parameters
|
|
261
|
+
----------
|
|
262
|
+
data : Any
|
|
263
|
+
Data to emit to stderr.
|
|
264
|
+
"""
|
|
265
|
+
output = json.dumps(data, default=str)
|
|
266
|
+
sys.stderr.write(output + "\n")
|
|
267
|
+
sys.stderr.flush()
|
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""PaginationHelper — manages --page-size, --page-token, --batch-pages (SRS §3.5).
|
|
3
|
+
|
|
4
|
+
Handles paginated SDK responses and emits metadata to stderr
|
|
5
|
+
with pagination cursors per ADR-005.
|
|
6
|
+
|
|
7
|
+
Default: return first page only with page_token on stderr.
|
|
8
|
+
Batch mode: agent specifies number of pages (max 40).
|
|
9
|
+
|
|
10
|
+
Environment variables:
|
|
11
|
+
- FOUNDRY_AGENTIC_CLI_DEFAULT_PAGE_SIZE (default: 100)
|
|
12
|
+
- FOUNDRY_AGENTIC_CLI_MAX_BATCH_PAGES (default: 40)
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
import json
|
|
16
|
+
import os
|
|
17
|
+
import sys
|
|
18
|
+
from typing import Any
|
|
19
|
+
|
|
20
|
+
from pal_found_cli.common.log_setup import METADATA_SEPARATOR
|
|
21
|
+
|
|
22
|
+
# Default values (from SRS-001 §5)
|
|
23
|
+
DEFAULT_PAGE_SIZE = int(os.environ.get("FOUNDRY_AGENTIC_CLI_DEFAULT_PAGE_SIZE", "100"))
|
|
24
|
+
HARD_MAX_BATCH_PAGES = 40
|
|
25
|
+
MAX_BATCH_PAGES = min(
|
|
26
|
+
int(
|
|
27
|
+
os.environ.get("FOUNDRY_AGENTIC_CLI_MAX_BATCH_PAGES", str(HARD_MAX_BATCH_PAGES))
|
|
28
|
+
),
|
|
29
|
+
HARD_MAX_BATCH_PAGES,
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
class PaginationHelper:
|
|
34
|
+
"""Manages pagination for SDK calls.
|
|
35
|
+
|
|
36
|
+
Parameters
|
|
37
|
+
----------
|
|
38
|
+
page_size : int or None
|
|
39
|
+
Number of items per page. Defaults to FOUNDRY_AGENTIC_CLI_DEFAULT_PAGE_SIZE (100).
|
|
40
|
+
page_token : str or None
|
|
41
|
+
Resume from this cursor.
|
|
42
|
+
batch_pages : int or None
|
|
43
|
+
Number of pages to retrieve (default 1, max FOUNDRY_AGENTIC_CLI_MAX_BATCH_PAGES).
|
|
44
|
+
|
|
45
|
+
Usage
|
|
46
|
+
-----
|
|
47
|
+
>>> helper = PaginationHelper(page_size=50, page_token=None, batch_pages=1)
|
|
48
|
+
>>> result = await helper.paginate(client_method, helper)
|
|
49
|
+
>>> helper.emit_metadata() # writes to stderr
|
|
50
|
+
"""
|
|
51
|
+
|
|
52
|
+
def __init__(
|
|
53
|
+
self,
|
|
54
|
+
page_size: int | None = None,
|
|
55
|
+
page_token: str | None = None,
|
|
56
|
+
batch_pages: int | None = None,
|
|
57
|
+
) -> None:
|
|
58
|
+
self.page_size = self._validate_positive_int(
|
|
59
|
+
"page_size",
|
|
60
|
+
page_size if page_size is not None else DEFAULT_PAGE_SIZE,
|
|
61
|
+
)
|
|
62
|
+
self.page_token = page_token
|
|
63
|
+
raw_batch = self._validate_positive_int(
|
|
64
|
+
"batch_pages",
|
|
65
|
+
batch_pages if batch_pages is not None else 1,
|
|
66
|
+
)
|
|
67
|
+
self.batch_pages = min(raw_batch, MAX_BATCH_PAGES)
|
|
68
|
+
self._next_page_token: str | None = None
|
|
69
|
+
self._total_items: int = 0
|
|
70
|
+
self._pages_fetched: int = 0
|
|
71
|
+
|
|
72
|
+
@staticmethod
|
|
73
|
+
def _validate_positive_int(name: str, value: int) -> int:
|
|
74
|
+
"""Validate that a pagination argument is a positive integer."""
|
|
75
|
+
if isinstance(value, bool) or not isinstance(value, int):
|
|
76
|
+
raise TypeError(f"{name} must be a positive integer")
|
|
77
|
+
if value <= 0:
|
|
78
|
+
raise ValueError(f"{name} must be a positive integer")
|
|
79
|
+
return value
|
|
80
|
+
|
|
81
|
+
@property
|
|
82
|
+
def next_page_token(self) -> str | None:
|
|
83
|
+
"""Cursor for next page."""
|
|
84
|
+
return self._next_page_token
|
|
85
|
+
|
|
86
|
+
@property
|
|
87
|
+
def total_items(self) -> int:
|
|
88
|
+
"""Total items fetched across all pages."""
|
|
89
|
+
return self._total_items
|
|
90
|
+
|
|
91
|
+
@property
|
|
92
|
+
def pages_fetched(self) -> int:
|
|
93
|
+
"""Number of pages fetched."""
|
|
94
|
+
return self._pages_fetched
|
|
95
|
+
|
|
96
|
+
def get_sdk_params(self) -> dict[str, Any]:
|
|
97
|
+
"""Get pagination parameters for SDK call.
|
|
98
|
+
|
|
99
|
+
Returns
|
|
100
|
+
-------
|
|
101
|
+
dict
|
|
102
|
+
SDK-compatible pagination kwargs.
|
|
103
|
+
"""
|
|
104
|
+
params: dict[str, Any] = {}
|
|
105
|
+
if self.page_size is not None:
|
|
106
|
+
params["page_size"] = self.page_size
|
|
107
|
+
if self.page_token is not None:
|
|
108
|
+
params["page_token"] = self.page_token
|
|
109
|
+
return params
|
|
110
|
+
|
|
111
|
+
def _extract_items(self, response: Any) -> list[Any]:
|
|
112
|
+
"""Extract items list from SDK response.
|
|
113
|
+
|
|
114
|
+
Parameters
|
|
115
|
+
----------
|
|
116
|
+
response : Any
|
|
117
|
+
SDK response object.
|
|
118
|
+
|
|
119
|
+
Returns
|
|
120
|
+
-------
|
|
121
|
+
list
|
|
122
|
+
List of items from the response.
|
|
123
|
+
"""
|
|
124
|
+
if isinstance(response, dict):
|
|
125
|
+
return response.get("items", []) or []
|
|
126
|
+
if isinstance(response, list):
|
|
127
|
+
return response
|
|
128
|
+
if hasattr(response, "items"):
|
|
129
|
+
return getattr(response, "items", []) or []
|
|
130
|
+
return [response] if response else []
|
|
131
|
+
|
|
132
|
+
def _extract_next_token(self, response: Any) -> str | None:
|
|
133
|
+
"""Extract next page token from SDK response.
|
|
134
|
+
|
|
135
|
+
Parameters
|
|
136
|
+
----------
|
|
137
|
+
response : Any
|
|
138
|
+
SDK response object.
|
|
139
|
+
|
|
140
|
+
Returns
|
|
141
|
+
-------
|
|
142
|
+
str or None
|
|
143
|
+
Next page token.
|
|
144
|
+
"""
|
|
145
|
+
if isinstance(response, dict):
|
|
146
|
+
return response.get("next_page_token") or response.get("nextPageToken")
|
|
147
|
+
if hasattr(response, "next_page_token"):
|
|
148
|
+
return getattr(response, "next_page_token", None)
|
|
149
|
+
if hasattr(response, "nextPageToken"):
|
|
150
|
+
return getattr(response, "nextPageToken", None)
|
|
151
|
+
return None
|
|
152
|
+
|
|
153
|
+
async def paginate(
|
|
154
|
+
self,
|
|
155
|
+
call_func,
|
|
156
|
+
**call_kwargs,
|
|
157
|
+
) -> list[Any]:
|
|
158
|
+
"""Execute paginated SDK calls.
|
|
159
|
+
|
|
160
|
+
Parameters
|
|
161
|
+
----------
|
|
162
|
+
call_func : callable
|
|
163
|
+
Async SDK method to call.
|
|
164
|
+
**call_kwargs
|
|
165
|
+
Additional kwargs to pass to SDK method.
|
|
166
|
+
|
|
167
|
+
Returns
|
|
168
|
+
-------
|
|
169
|
+
list
|
|
170
|
+
Aggregated items from all pages.
|
|
171
|
+
"""
|
|
172
|
+
all_items: list[Any] = []
|
|
173
|
+
token = self.page_token
|
|
174
|
+
|
|
175
|
+
for _ in range(self.batch_pages):
|
|
176
|
+
# Build params
|
|
177
|
+
params = dict(call_kwargs)
|
|
178
|
+
if self.page_size is not None:
|
|
179
|
+
params["page_size"] = self.page_size
|
|
180
|
+
if token is not None:
|
|
181
|
+
params["page_token"] = token
|
|
182
|
+
|
|
183
|
+
response = await call_func(**params)
|
|
184
|
+
items = self._extract_items(response)
|
|
185
|
+
all_items.extend(items)
|
|
186
|
+
self._total_items += len(items)
|
|
187
|
+
self._pages_fetched += 1
|
|
188
|
+
|
|
189
|
+
token = self._extract_next_token(response)
|
|
190
|
+
if token is None:
|
|
191
|
+
break
|
|
192
|
+
|
|
193
|
+
self._next_page_token = token
|
|
194
|
+
return all_items
|
|
195
|
+
|
|
196
|
+
def emit_metadata(self) -> None:
|
|
197
|
+
"""Emit pagination metadata to stderr (ADR-005).
|
|
198
|
+
|
|
199
|
+
Writes METADATA_SEPARATOR followed by JSON metadata.
|
|
200
|
+
"""
|
|
201
|
+
metadata: dict[str, Any] = {
|
|
202
|
+
"pages_fetched": self._pages_fetched,
|
|
203
|
+
"total_items": self._total_items,
|
|
204
|
+
}
|
|
205
|
+
if self._next_page_token:
|
|
206
|
+
metadata["next_page_token"] = self._next_page_token
|
|
207
|
+
if self.page_size:
|
|
208
|
+
metadata["page_size"] = self.page_size
|
|
209
|
+
|
|
210
|
+
sys.stderr.write(METADATA_SEPARATOR + "\n")
|
|
211
|
+
sys.stderr.write(json.dumps(metadata) + "\n")
|
|
212
|
+
sys.stderr.flush()
|