maxc-cli 0.5.0__tar.gz → 0.6.0__tar.gz
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.
- {maxc_cli-0.5.0 → maxc_cli-0.6.0}/PKG-INFO +74 -14
- {maxc_cli-0.5.0 → maxc_cli-0.6.0}/README.md +73 -13
- {maxc_cli-0.5.0 → maxc_cli-0.6.0}/src/maxc_cli/__init__.py +1 -1
- {maxc_cli-0.5.0 → maxc_cli-0.6.0}/src/maxc_cli/_samples.py +12 -0
- {maxc_cli-0.5.0 → maxc_cli-0.6.0}/src/maxc_cli/app.py +97 -15
- {maxc_cli-0.5.0 → maxc_cli-0.6.0}/src/maxc_cli/audit.py +1 -0
- {maxc_cli-0.5.0 → maxc_cli-0.6.0}/src/maxc_cli/auth_providers.py +31 -1
- {maxc_cli-0.5.0 → maxc_cli-0.6.0}/src/maxc_cli/backend/data.py +7 -4
- {maxc_cli-0.5.0 → maxc_cli-0.6.0}/src/maxc_cli/backend/job.py +4 -2
- {maxc_cli-0.5.0 → maxc_cli-0.6.0}/src/maxc_cli/backend/odps.py +2 -0
- {maxc_cli-0.5.0 → maxc_cli-0.6.0}/src/maxc_cli/backend/query.py +33 -11
- maxc_cli-0.6.0/src/maxc_cli/backend/semantic.py +124 -0
- {maxc_cli-0.5.0 → maxc_cli-0.6.0}/src/maxc_cli/cli.py +174 -31
- {maxc_cli-0.5.0 → maxc_cli-0.6.0}/src/maxc_cli/exceptions.py +47 -22
- {maxc_cli-0.5.0 → maxc_cli-0.6.0}/src/maxc_cli/helpers.py +6 -3
- {maxc_cli-0.5.0 → maxc_cli-0.6.0}/src/maxc_cli/models.py +131 -16
- {maxc_cli-0.5.0 → maxc_cli-0.6.0}/src/maxc_cli/output.py +82 -20
- maxc_cli-0.6.0/src/maxc_cli/proxy_auth.py +16 -0
- maxc_cli-0.6.0/src/maxc_cli/semantic.py +239 -0
- maxc_cli-0.6.0/src/maxc_cli/semantic_management.py +159 -0
- {maxc_cli-0.5.0 → maxc_cli-0.6.0}/src/maxc_cli/skills/SKILL.md +60 -22
- {maxc_cli-0.5.0 → maxc_cli-0.6.0}/src/maxc_cli/skills/references/bootstrap-auth.md +32 -9
- {maxc_cli-0.5.0 → maxc_cli-0.6.0}/src/maxc_cli/skills/references/bootstrap-flow.md +4 -3
- {maxc_cli-0.5.0 → maxc_cli-0.6.0}/src/maxc_cli/skills/references/command-patterns.md +39 -7
- {maxc_cli-0.5.0 → maxc_cli-0.6.0}/src/maxc_cli/skills/references/json-output-format.md +78 -15
- {maxc_cli-0.5.0 → maxc_cli-0.6.0}/src/maxc_cli/skills/references/maxcompute-sql-notes.md +33 -11
- {maxc_cli-0.5.0 → maxc_cli-0.6.0}/src/maxc_cli/skills/references/red-lines.md +10 -8
- maxc_cli-0.6.0/src/maxc_cli/skills/references/semantic-packages.md +85 -0
- {maxc_cli-0.5.0 → maxc_cli-0.6.0}/src/maxc_cli/skills/references/setup-install.md +12 -7
- {maxc_cli-0.5.0 → maxc_cli-0.6.0}/src/maxc_cli/skills/references/sql-common-errors.md +9 -7
- {maxc_cli-0.5.0 → maxc_cli-0.6.0}/src/maxc_cli/state_permissions.py +11 -3
- maxc_cli-0.6.0/src/maxc_cli/utils.py +1881 -0
- {maxc_cli-0.5.0 → maxc_cli-0.6.0}/src/maxc_cli.egg-info/PKG-INFO +74 -14
- {maxc_cli-0.5.0 → maxc_cli-0.6.0}/src/maxc_cli.egg-info/SOURCES.txt +9 -0
- {maxc_cli-0.5.0 → maxc_cli-0.6.0}/tests/test_agent_hints_and_cli.py +59 -10
- {maxc_cli-0.5.0 → maxc_cli-0.6.0}/tests/test_agent_skill_commands_context.py +46 -3
- {maxc_cli-0.5.0 → maxc_cli-0.6.0}/tests/test_ai_native_contract_regressions.py +71 -3
- {maxc_cli-0.5.0 → maxc_cli-0.6.0}/tests/test_cli_mock.py +9 -1
- maxc_cli-0.6.0/tests/test_effective_hints_contract.py +301 -0
- {maxc_cli-0.5.0 → maxc_cli-0.6.0}/tests/test_exit_codes.py +40 -4
- {maxc_cli-0.5.0 → maxc_cli-0.6.0}/tests/test_job_improvements.py +85 -1
- {maxc_cli-0.5.0 → maxc_cli-0.6.0}/tests/test_manifest_runtime_contract.py +1 -1
- {maxc_cli-0.5.0 → maxc_cli-0.6.0}/tests/test_meta_schema_and_partition_cols.py +24 -12
- {maxc_cli-0.5.0 → maxc_cli-0.6.0}/tests/test_output_action_safety.py +12 -6
- {maxc_cli-0.5.0 → maxc_cli-0.6.0}/tests/test_output_format_contract.py +18 -2
- {maxc_cli-0.5.0 → maxc_cli-0.6.0}/tests/test_phase1_improvements.py +32 -16
- maxc_cli-0.6.0/tests/test_proxy_auth.py +123 -0
- {maxc_cli-0.5.0 → maxc_cli-0.6.0}/tests/test_query_auto_promote.py +33 -0
- maxc_cli-0.6.0/tests/test_semantic_management.py +301 -0
- maxc_cli-0.6.0/tests/test_semantic_transport.py +141 -0
- maxc_cli-0.6.0/tests/test_setting_parser.py +934 -0
- {maxc_cli-0.5.0 → maxc_cli-0.6.0}/tests/test_state_permissions.py +35 -0
- maxc_cli-0.5.0/src/maxc_cli/utils.py +0 -795
- maxc_cli-0.5.0/tests/test_setting_parser.py +0 -388
- {maxc_cli-0.5.0 → maxc_cli-0.6.0}/MANIFEST.in +0 -0
- {maxc_cli-0.5.0 → maxc_cli-0.6.0}/pyproject.toml +0 -0
- {maxc_cli-0.5.0 → maxc_cli-0.6.0}/scripts/pyinstaller_entry.py +0 -0
- {maxc_cli-0.5.0 → maxc_cli-0.6.0}/scripts/regression_test.py +0 -0
- {maxc_cli-0.5.0 → maxc_cli-0.6.0}/setup.cfg +0 -0
- {maxc_cli-0.5.0 → maxc_cli-0.6.0}/setup.py +0 -0
- {maxc_cli-0.5.0 → maxc_cli-0.6.0}/src/maxc_cli/__main__.py +0 -0
- {maxc_cli-0.5.0 → maxc_cli-0.6.0}/src/maxc_cli/agent_platforms.py +0 -0
- {maxc_cli-0.5.0 → maxc_cli-0.6.0}/src/maxc_cli/auth_continuation.py +0 -0
- {maxc_cli-0.5.0 → maxc_cli-0.6.0}/src/maxc_cli/backend/__init__.py +0 -0
- {maxc_cli-0.5.0 → maxc_cli-0.6.0}/src/maxc_cli/backend/auth.py +0 -0
- {maxc_cli-0.5.0 → maxc_cli-0.6.0}/src/maxc_cli/backend/catalog.py +0 -0
- {maxc_cli-0.5.0 → maxc_cli-0.6.0}/src/maxc_cli/backend/meta.py +0 -0
- {maxc_cli-0.5.0 → maxc_cli-0.6.0}/src/maxc_cli/cache.py +0 -0
- {maxc_cli-0.5.0 → maxc_cli-0.6.0}/src/maxc_cli/catalog_bootstrap.py +0 -0
- {maxc_cli-0.5.0 → maxc_cli-0.6.0}/src/maxc_cli/config.py +0 -0
- {maxc_cli-0.5.0 → maxc_cli-0.6.0}/src/maxc_cli/help_format.py +0 -0
- {maxc_cli-0.5.0 → maxc_cli-0.6.0}/src/maxc_cli/job_ids.py +0 -0
- {maxc_cli-0.5.0 → maxc_cli-0.6.0}/src/maxc_cli/masking.py +0 -0
- {maxc_cli-0.5.0 → maxc_cli-0.6.0}/src/maxc_cli/oauth.py +0 -0
- {maxc_cli-0.5.0 → maxc_cli-0.6.0}/src/maxc_cli/odps_runtime.py +0 -0
- {maxc_cli-0.5.0 → maxc_cli-0.6.0}/src/maxc_cli/setting_parser.py +0 -0
- {maxc_cli-0.5.0 → maxc_cli-0.6.0}/src/maxc_cli/skills/agents/openai.yaml +0 -0
- {maxc_cli-0.5.0 → maxc_cli-0.6.0}/src/maxc_cli/skills/references/maxcompute-select-guide.md +0 -0
- {maxc_cli-0.5.0 → maxc_cli-0.6.0}/src/maxc_cli/skills/references/partition-guide.md +0 -0
- {maxc_cli-0.5.0 → maxc_cli-0.6.0}/src/maxc_cli/skills/references/sql-query-patterns.md +0 -0
- {maxc_cli-0.5.0 → maxc_cli-0.6.0}/src/maxc_cli/skills/references/text2sql-principles.md +0 -0
- {maxc_cli-0.5.0 → maxc_cli-0.6.0}/src/maxc_cli/store.py +0 -0
- {maxc_cli-0.5.0 → maxc_cli-0.6.0}/src/maxc_cli.egg-info/dependency_links.txt +0 -0
- {maxc_cli-0.5.0 → maxc_cli-0.6.0}/src/maxc_cli.egg-info/entry_points.txt +0 -0
- {maxc_cli-0.5.0 → maxc_cli-0.6.0}/src/maxc_cli.egg-info/requires.txt +0 -0
- {maxc_cli-0.5.0 → maxc_cli-0.6.0}/src/maxc_cli.egg-info/top_level.txt +0 -0
- {maxc_cli-0.5.0 → maxc_cli-0.6.0}/tests/test_agent_platforms.py +0 -0
- {maxc_cli-0.5.0 → maxc_cli-0.6.0}/tests/test_agent_skill_commands.py +0 -0
- {maxc_cli-0.5.0 → maxc_cli-0.6.0}/tests/test_auth_logout.py +0 -0
- {maxc_cli-0.5.0 → maxc_cli-0.6.0}/tests/test_backend_auth.py +0 -0
- {maxc_cli-0.5.0 → maxc_cli-0.6.0}/tests/test_backend_data.py +0 -0
- {maxc_cli-0.5.0 → maxc_cli-0.6.0}/tests/test_backend_data_serialization.py +0 -0
- {maxc_cli-0.5.0 → maxc_cli-0.6.0}/tests/test_backend_meta.py +0 -0
- {maxc_cli-0.5.0 → maxc_cli-0.6.0}/tests/test_build_release_archive_compat.py +0 -0
- {maxc_cli-0.5.0 → maxc_cli-0.6.0}/tests/test_build_release_script.py +0 -0
- {maxc_cli-0.5.0 → maxc_cli-0.6.0}/tests/test_cache.py +0 -0
- {maxc_cli-0.5.0 → maxc_cli-0.6.0}/tests/test_catalog.py +0 -0
- {maxc_cli-0.5.0 → maxc_cli-0.6.0}/tests/test_catalog_bootstrap.py +0 -0
- {maxc_cli-0.5.0 → maxc_cli-0.6.0}/tests/test_cli_arg_validation.py +0 -0
- {maxc_cli-0.5.0 → maxc_cli-0.6.0}/tests/test_cli_query_parse_and_sanitize.py +0 -0
- {maxc_cli-0.5.0 → maxc_cli-0.6.0}/tests/test_compat.py +0 -0
- {maxc_cli-0.5.0 → maxc_cli-0.6.0}/tests/test_config_atomic_write.py +0 -0
- {maxc_cli-0.5.0 → maxc_cli-0.6.0}/tests/test_e2e_smoke.py +0 -0
- {maxc_cli-0.5.0 → maxc_cli-0.6.0}/tests/test_envelope_shape.py +0 -0
- {maxc_cli-0.5.0 → maxc_cli-0.6.0}/tests/test_error_self_correction.py +0 -0
- {maxc_cli-0.5.0 → maxc_cli-0.6.0}/tests/test_error_translation.py +0 -0
- {maxc_cli-0.5.0 → maxc_cli-0.6.0}/tests/test_external_auth.py +0 -0
- {maxc_cli-0.5.0 → maxc_cli-0.6.0}/tests/test_flag_hoist.py +0 -0
- {maxc_cli-0.5.0 → maxc_cli-0.6.0}/tests/test_help_format.py +0 -0
- {maxc_cli-0.5.0 → maxc_cli-0.6.0}/tests/test_help_version_e2e.py +0 -0
- {maxc_cli-0.5.0 → maxc_cli-0.6.0}/tests/test_helpers.py +0 -0
- {maxc_cli-0.5.0 → maxc_cli-0.6.0}/tests/test_helpers_csv.py +0 -0
- {maxc_cli-0.5.0 → maxc_cli-0.6.0}/tests/test_installer_contracts.py +0 -0
- {maxc_cli-0.5.0 → maxc_cli-0.6.0}/tests/test_integration.py +0 -0
- {maxc_cli-0.5.0 → maxc_cli-0.6.0}/tests/test_integration_real.py +0 -0
- {maxc_cli-0.5.0 → maxc_cli-0.6.0}/tests/test_job_store_durability.py +0 -0
- {maxc_cli-0.5.0 → maxc_cli-0.6.0}/tests/test_masking.py +0 -0
- {maxc_cli-0.5.0 → maxc_cli-0.6.0}/tests/test_oauth.py +0 -0
- {maxc_cli-0.5.0 → maxc_cli-0.6.0}/tests/test_odps_runtime.py +0 -0
- {maxc_cli-0.5.0 → maxc_cli-0.6.0}/tests/test_packaging_metadata.py +0 -0
- {maxc_cli-0.5.0 → maxc_cli-0.6.0}/tests/test_pyinstaller_bundle.py +0 -0
- {maxc_cli-0.5.0 → maxc_cli-0.6.0}/tests/test_python39_compat.py +0 -0
- {maxc_cli-0.5.0 → maxc_cli-0.6.0}/tests/test_query_result_csv_fallback.py +0 -0
- {maxc_cli-0.5.0 → maxc_cli-0.6.0}/tests/test_semantic_scope.py +0 -0
- {maxc_cli-0.5.0 → maxc_cli-0.6.0}/tests/test_skill_cli_consistency.py +0 -0
- {maxc_cli-0.5.0 → maxc_cli-0.6.0}/tests/test_skill_eval.py +0 -0
- {maxc_cli-0.5.0 → maxc_cli-0.6.0}/tests/test_skill_renderer.py +0 -0
- {maxc_cli-0.5.0 → maxc_cli-0.6.0}/tests/test_startup_imports.py +0 -0
- {maxc_cli-0.5.0 → maxc_cli-0.6.0}/tests/test_state_portability.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: maxc-cli
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.6.0
|
|
4
4
|
Summary: Agent-native MaxCompute CLI for external coding agents
|
|
5
5
|
Classifier: Programming Language :: Python :: 3
|
|
6
6
|
Classifier: Programming Language :: Python :: 3.9
|
|
@@ -31,7 +31,7 @@ MaxCompute 元数据、SQL、作业、权限和数据传输等数据面操作。
|
|
|
31
31
|
### 公共云(推荐)
|
|
32
32
|
|
|
33
33
|
```bash
|
|
34
|
-
# Alibaba Cloud CLI
|
|
34
|
+
# aliyun maxc 需要 Alibaba Cloud CLI >= 3.3.19
|
|
35
35
|
aliyun version
|
|
36
36
|
|
|
37
37
|
# OAuth 是交互式登录的首选方式,不需要把长期 AK/SK 放进命令行
|
|
@@ -49,6 +49,9 @@ aliyun maxc query cost "SELECT * FROM schema.table WHERE ds='20260415'" --json
|
|
|
49
49
|
aliyun maxc query "SELECT * FROM schema.table WHERE ds='20260415'" --json
|
|
50
50
|
```
|
|
51
51
|
|
|
52
|
+
支持自升级的非 Homebrew Alibaba Cloud CLI 3.3.5+ 可在用户确认后运行
|
|
53
|
+
`aliyun upgrade`;更早版本、Homebrew 安装或缺失 CLI 请按官方安装方式更新。
|
|
54
|
+
|
|
52
55
|
如果 Alibaba Cloud CLI 扩展不可用,或明确需要 PyPI 版本,可使用独立入口。
|
|
53
56
|
独立入口要求 Python 3.9 或更高版本:
|
|
54
57
|
|
|
@@ -110,13 +113,15 @@ Agent 启动时生成一次 32 位小写十六进制 session ID,并在整个
|
|
|
110
113
|
UA="AlibabaCloud-Agent-Skills/alibabacloud-maxcompute-cli/<session-id>"
|
|
111
114
|
```
|
|
112
115
|
|
|
113
|
-
|
|
116
|
+
每条调用云 API 的 `aliyun maxc` 命令都追加 `--user-agent "$UA"`;本地
|
|
117
|
+
help、`agent context`、`agent manifest`、`session show` 和 `cache status`
|
|
118
|
+
可以省略。然后依次运行:
|
|
114
119
|
|
|
115
120
|
```bash
|
|
116
|
-
aliyun maxc agent context --
|
|
117
|
-
aliyun maxc agent manifest --
|
|
121
|
+
aliyun maxc agent context --json # 仅检查本地版本、配置和能力;不访问网络
|
|
122
|
+
aliyun maxc agent manifest --json # 从实时 parser 生成命令、参数和副作用清单
|
|
118
123
|
aliyun maxc agent doctor --online --user-agent "$UA" --json # 验证身份与后端可达性
|
|
119
|
-
aliyun maxc agent skill --
|
|
124
|
+
aliyun maxc agent skill --json # Skill 路径、名称与 min_cli_version
|
|
120
125
|
```
|
|
121
126
|
|
|
122
127
|
只有 `agent doctor --online` 能证明远端已就绪;不要把 `agent context` 中的
|
|
@@ -139,10 +144,19 @@ aliyun maxc agent skill --user-agent "$UA" --json # Skill 路径、
|
|
|
139
144
|
{
|
|
140
145
|
"id": "meta.search",
|
|
141
146
|
"title": "Search tables",
|
|
142
|
-
"command": "maxc meta search <keyword> --json",
|
|
147
|
+
"command": "maxc --user-agent <user_agent> meta search <keyword> --json",
|
|
143
148
|
"executable": false,
|
|
144
|
-
"placeholders": {
|
|
145
|
-
|
|
149
|
+
"placeholders": {
|
|
150
|
+
"keyword": "<keyword>",
|
|
151
|
+
"user_agent": "<user_agent>"
|
|
152
|
+
},
|
|
153
|
+
"args_schema": {
|
|
154
|
+
"user_agent": {
|
|
155
|
+
"type": "string",
|
|
156
|
+
"description": "Reuse the User-Agent generated once for the current Agent session.",
|
|
157
|
+
"pattern": "^AlibabaCloud-Agent-Skills/alibabacloud-maxcompute-cli/[0-9a-f]{32}$"
|
|
158
|
+
}
|
|
159
|
+
},
|
|
146
160
|
"effect": "read",
|
|
147
161
|
"confirmation_required": false,
|
|
148
162
|
"agent_allowed": true
|
|
@@ -178,7 +192,7 @@ maxc meta describe my_table --json
|
|
|
178
192
|
"mode": "read_only",
|
|
179
193
|
"force": false,
|
|
180
194
|
"allowed_operations": ["SELECT"],
|
|
181
|
-
"effective_hints": {
|
|
195
|
+
"effective_hints": {},
|
|
182
196
|
"policy_decision": "allowed"
|
|
183
197
|
}
|
|
184
198
|
```
|
|
@@ -219,10 +233,14 @@ src/maxc_cli/
|
|
|
219
233
|
|
|
220
234
|
## 限制
|
|
221
235
|
|
|
222
|
-
-
|
|
223
|
-
|
|
224
|
-
`
|
|
225
|
-
|
|
236
|
+
- **查询安全**:SQL 默认按只读请求处理。只有用户明确要求具体 DDL/DML
|
|
237
|
+
时,才核对完整 statement、project、schema、目标和影响,通过 `query`
|
|
238
|
+
或 `job submit` 一次提交一条语句并显式增加 `--force`。`data upload`、
|
|
239
|
+
`data download --overwrite` 和 `job cancel` 是独立的有副作用操作,同样需
|
|
240
|
+
与影响相匹配的明确授权。权限、账号、project、system、resource、package
|
|
241
|
+
等管理 SQL 和未知语法不在公共 `--force` 正向允许列表中。前置 `SET`
|
|
242
|
+
同样属于执行上下文:项目安全、访问控制和脱敏参数始终被阻断,强制写入
|
|
243
|
+
仅接受已审查的语句级执行参数。
|
|
226
244
|
- **OAuth 优先**:公共云交互式登录优先 OAuth。只有运行环境明确要求时才用
|
|
227
245
|
AK/SK、STS、环境变量或外部凭证进程。直接 AK/SK 会写入
|
|
228
246
|
`~/.maxc/config.yaml`(文件权限 0600)。OAuth 需要账号/组织已分配官方
|
|
@@ -251,3 +269,45 @@ src/maxc_cli/
|
|
|
251
269
|
pip install -e .
|
|
252
270
|
pytest tests/ -m unit
|
|
253
271
|
```
|
|
272
|
+
|
|
273
|
+
## Trusted egress authentication
|
|
274
|
+
|
|
275
|
+
In a sandbox whose trusted egress injects ODPS credentials, explicitly use:
|
|
276
|
+
|
|
277
|
+
```bash
|
|
278
|
+
aliyun maxc auth login-proxy --project <project> --odps-endpoint <https-endpoint> --json
|
|
279
|
+
aliyun maxc agent doctor --online --json
|
|
280
|
+
```
|
|
281
|
+
|
|
282
|
+
This saves `auth.provider: proxy` and routing information only. MaxC does not
|
|
283
|
+
load AK/SK, STS, OAuth or credential helpers, and sends no Authorization or ODPS
|
|
284
|
+
token headers. The egress must authenticate the sandbox and inject credentials.
|
|
285
|
+
TLS verification remains enabled; preserve the egress-provided CA environment.
|
|
286
|
+
Only proceed when `agent doctor --online` reports `data.ready=true`. A saved
|
|
287
|
+
configuration (`--no-validate`) alone does not prove connectivity or identity.
|
|
288
|
+
The egress must cover metadata, SQL polling and result/Tunnel endpoints actually
|
|
289
|
+
used by the workload; ordinary endpoints without injection will reject requests.
|
|
290
|
+
To return to local signing, explicitly run the appropriate `auth login` command.
|
|
291
|
+
|
|
292
|
+
## Versioned semantic packages
|
|
293
|
+
|
|
294
|
+
`maxc semantic` manages remote Catalog packages using the current connection.
|
|
295
|
+
Specify the verified main-account ID with `--namespace`. Available operations:
|
|
296
|
+
`list`, `get`, `revisions`, `export`, `create`, `diff`, `apply`, `publish`, `delete`.
|
|
297
|
+
`meta semantic` continues to manage local annotations.
|
|
298
|
+
|
|
299
|
+
Export a USER_DRAFT, edit its `object`/`content`, review `diff`, then use its
|
|
300
|
+
`planDigest` with `apply --plan-digest ... --yes`. Publishing is a separate
|
|
301
|
+
`publish --expected-spec-id ... --expected-revision ... --yes` operation.
|
|
302
|
+
Run each command's `--help` for required arguments.
|
|
303
|
+
|
|
304
|
+
The editable JSON format is `maxc.semantic/v1`; its identity and revision fields
|
|
305
|
+
bind edits to one account, endpoint and object. Whole sections replace prior
|
|
306
|
+
content; omitted sections remain unchanged and explicit empty arrays clear them.
|
|
307
|
+
Exports strip server-generated table IDs and preserve unknown nested fields.
|
|
308
|
+
Source files and output paths must not traverse symlinks/reparse points.
|
|
309
|
+
|
|
310
|
+
Metadata edits lack server CAS; publication lacks idempotency keys. An uncertain
|
|
311
|
+
write must be reconciled before retrying. Publication checks are not SQL or
|
|
312
|
+
business validation. History lists the latest 20 summaries, and an immutable
|
|
313
|
+
revision read does not guarantee historical DataBridge analysis.
|
|
@@ -9,7 +9,7 @@ MaxCompute 元数据、SQL、作业、权限和数据传输等数据面操作。
|
|
|
9
9
|
### 公共云(推荐)
|
|
10
10
|
|
|
11
11
|
```bash
|
|
12
|
-
# Alibaba Cloud CLI
|
|
12
|
+
# aliyun maxc 需要 Alibaba Cloud CLI >= 3.3.19
|
|
13
13
|
aliyun version
|
|
14
14
|
|
|
15
15
|
# OAuth 是交互式登录的首选方式,不需要把长期 AK/SK 放进命令行
|
|
@@ -27,6 +27,9 @@ aliyun maxc query cost "SELECT * FROM schema.table WHERE ds='20260415'" --json
|
|
|
27
27
|
aliyun maxc query "SELECT * FROM schema.table WHERE ds='20260415'" --json
|
|
28
28
|
```
|
|
29
29
|
|
|
30
|
+
支持自升级的非 Homebrew Alibaba Cloud CLI 3.3.5+ 可在用户确认后运行
|
|
31
|
+
`aliyun upgrade`;更早版本、Homebrew 安装或缺失 CLI 请按官方安装方式更新。
|
|
32
|
+
|
|
30
33
|
如果 Alibaba Cloud CLI 扩展不可用,或明确需要 PyPI 版本,可使用独立入口。
|
|
31
34
|
独立入口要求 Python 3.9 或更高版本:
|
|
32
35
|
|
|
@@ -88,13 +91,15 @@ Agent 启动时生成一次 32 位小写十六进制 session ID,并在整个
|
|
|
88
91
|
UA="AlibabaCloud-Agent-Skills/alibabacloud-maxcompute-cli/<session-id>"
|
|
89
92
|
```
|
|
90
93
|
|
|
91
|
-
|
|
94
|
+
每条调用云 API 的 `aliyun maxc` 命令都追加 `--user-agent "$UA"`;本地
|
|
95
|
+
help、`agent context`、`agent manifest`、`session show` 和 `cache status`
|
|
96
|
+
可以省略。然后依次运行:
|
|
92
97
|
|
|
93
98
|
```bash
|
|
94
|
-
aliyun maxc agent context --
|
|
95
|
-
aliyun maxc agent manifest --
|
|
99
|
+
aliyun maxc agent context --json # 仅检查本地版本、配置和能力;不访问网络
|
|
100
|
+
aliyun maxc agent manifest --json # 从实时 parser 生成命令、参数和副作用清单
|
|
96
101
|
aliyun maxc agent doctor --online --user-agent "$UA" --json # 验证身份与后端可达性
|
|
97
|
-
aliyun maxc agent skill --
|
|
102
|
+
aliyun maxc agent skill --json # Skill 路径、名称与 min_cli_version
|
|
98
103
|
```
|
|
99
104
|
|
|
100
105
|
只有 `agent doctor --online` 能证明远端已就绪;不要把 `agent context` 中的
|
|
@@ -117,10 +122,19 @@ aliyun maxc agent skill --user-agent "$UA" --json # Skill 路径、
|
|
|
117
122
|
{
|
|
118
123
|
"id": "meta.search",
|
|
119
124
|
"title": "Search tables",
|
|
120
|
-
"command": "maxc meta search <keyword> --json",
|
|
125
|
+
"command": "maxc --user-agent <user_agent> meta search <keyword> --json",
|
|
121
126
|
"executable": false,
|
|
122
|
-
"placeholders": {
|
|
123
|
-
|
|
127
|
+
"placeholders": {
|
|
128
|
+
"keyword": "<keyword>",
|
|
129
|
+
"user_agent": "<user_agent>"
|
|
130
|
+
},
|
|
131
|
+
"args_schema": {
|
|
132
|
+
"user_agent": {
|
|
133
|
+
"type": "string",
|
|
134
|
+
"description": "Reuse the User-Agent generated once for the current Agent session.",
|
|
135
|
+
"pattern": "^AlibabaCloud-Agent-Skills/alibabacloud-maxcompute-cli/[0-9a-f]{32}$"
|
|
136
|
+
}
|
|
137
|
+
},
|
|
124
138
|
"effect": "read",
|
|
125
139
|
"confirmation_required": false,
|
|
126
140
|
"agent_allowed": true
|
|
@@ -156,7 +170,7 @@ maxc meta describe my_table --json
|
|
|
156
170
|
"mode": "read_only",
|
|
157
171
|
"force": false,
|
|
158
172
|
"allowed_operations": ["SELECT"],
|
|
159
|
-
"effective_hints": {
|
|
173
|
+
"effective_hints": {},
|
|
160
174
|
"policy_decision": "allowed"
|
|
161
175
|
}
|
|
162
176
|
```
|
|
@@ -197,10 +211,14 @@ src/maxc_cli/
|
|
|
197
211
|
|
|
198
212
|
## 限制
|
|
199
213
|
|
|
200
|
-
-
|
|
201
|
-
|
|
202
|
-
`
|
|
203
|
-
|
|
214
|
+
- **查询安全**:SQL 默认按只读请求处理。只有用户明确要求具体 DDL/DML
|
|
215
|
+
时,才核对完整 statement、project、schema、目标和影响,通过 `query`
|
|
216
|
+
或 `job submit` 一次提交一条语句并显式增加 `--force`。`data upload`、
|
|
217
|
+
`data download --overwrite` 和 `job cancel` 是独立的有副作用操作,同样需
|
|
218
|
+
与影响相匹配的明确授权。权限、账号、project、system、resource、package
|
|
219
|
+
等管理 SQL 和未知语法不在公共 `--force` 正向允许列表中。前置 `SET`
|
|
220
|
+
同样属于执行上下文:项目安全、访问控制和脱敏参数始终被阻断,强制写入
|
|
221
|
+
仅接受已审查的语句级执行参数。
|
|
204
222
|
- **OAuth 优先**:公共云交互式登录优先 OAuth。只有运行环境明确要求时才用
|
|
205
223
|
AK/SK、STS、环境变量或外部凭证进程。直接 AK/SK 会写入
|
|
206
224
|
`~/.maxc/config.yaml`(文件权限 0600)。OAuth 需要账号/组织已分配官方
|
|
@@ -229,3 +247,45 @@ src/maxc_cli/
|
|
|
229
247
|
pip install -e .
|
|
230
248
|
pytest tests/ -m unit
|
|
231
249
|
```
|
|
250
|
+
|
|
251
|
+
## Trusted egress authentication
|
|
252
|
+
|
|
253
|
+
In a sandbox whose trusted egress injects ODPS credentials, explicitly use:
|
|
254
|
+
|
|
255
|
+
```bash
|
|
256
|
+
aliyun maxc auth login-proxy --project <project> --odps-endpoint <https-endpoint> --json
|
|
257
|
+
aliyun maxc agent doctor --online --json
|
|
258
|
+
```
|
|
259
|
+
|
|
260
|
+
This saves `auth.provider: proxy` and routing information only. MaxC does not
|
|
261
|
+
load AK/SK, STS, OAuth or credential helpers, and sends no Authorization or ODPS
|
|
262
|
+
token headers. The egress must authenticate the sandbox and inject credentials.
|
|
263
|
+
TLS verification remains enabled; preserve the egress-provided CA environment.
|
|
264
|
+
Only proceed when `agent doctor --online` reports `data.ready=true`. A saved
|
|
265
|
+
configuration (`--no-validate`) alone does not prove connectivity or identity.
|
|
266
|
+
The egress must cover metadata, SQL polling and result/Tunnel endpoints actually
|
|
267
|
+
used by the workload; ordinary endpoints without injection will reject requests.
|
|
268
|
+
To return to local signing, explicitly run the appropriate `auth login` command.
|
|
269
|
+
|
|
270
|
+
## Versioned semantic packages
|
|
271
|
+
|
|
272
|
+
`maxc semantic` manages remote Catalog packages using the current connection.
|
|
273
|
+
Specify the verified main-account ID with `--namespace`. Available operations:
|
|
274
|
+
`list`, `get`, `revisions`, `export`, `create`, `diff`, `apply`, `publish`, `delete`.
|
|
275
|
+
`meta semantic` continues to manage local annotations.
|
|
276
|
+
|
|
277
|
+
Export a USER_DRAFT, edit its `object`/`content`, review `diff`, then use its
|
|
278
|
+
`planDigest` with `apply --plan-digest ... --yes`. Publishing is a separate
|
|
279
|
+
`publish --expected-spec-id ... --expected-revision ... --yes` operation.
|
|
280
|
+
Run each command's `--help` for required arguments.
|
|
281
|
+
|
|
282
|
+
The editable JSON format is `maxc.semantic/v1`; its identity and revision fields
|
|
283
|
+
bind edits to one account, endpoint and object. Whole sections replace prior
|
|
284
|
+
content; omitted sections remain unchanged and explicit empty arrays clear them.
|
|
285
|
+
Exports strip server-generated table IDs and preserve unknown nested fields.
|
|
286
|
+
Source files and output paths must not traverse symlinks/reparse points.
|
|
287
|
+
|
|
288
|
+
Metadata edits lack server CAS; publication lacks idempotency keys. An uncertain
|
|
289
|
+
write must be reconciled before retrying. Publication checks are not SQL or
|
|
290
|
+
business validation. History lists the latest 20 summaries, and an immutable
|
|
291
|
+
revision read does not guarantee historical DataBridge analysis.
|
|
@@ -12,6 +12,15 @@ SAMPLES: dict[str, str] = {
|
|
|
12
12
|
'maxc query "SELECT 1"\n'
|
|
13
13
|
"maxc meta list-tables --project my_proj"
|
|
14
14
|
),
|
|
15
|
+
"semantic.list": "maxc semantic list --namespace <account_id> --json",
|
|
16
|
+
"semantic.get": "maxc semantic get sales --namespace <account_id> --source PUBLISHED --revision <revision> --json",
|
|
17
|
+
"semantic.revisions": "maxc semantic revisions sales --namespace <account_id> --json",
|
|
18
|
+
"semantic.export": "maxc semantic export sales --namespace <account_id> --output sales.json --json",
|
|
19
|
+
"semantic.create": "maxc semantic create sales --namespace <account_id> --file definition.json --json",
|
|
20
|
+
"semantic.diff": "maxc semantic diff sales --namespace <account_id> --file sales.json --json",
|
|
21
|
+
"semantic.apply": "maxc semantic apply sales --namespace <account_id> --file sales.json --plan-digest <reviewed_digest> --json",
|
|
22
|
+
"semantic.publish": "maxc semantic publish sales --namespace <account_id> --expected-spec-id <spec_id> --expected-revision <reviewed_revision> --json",
|
|
23
|
+
"semantic.delete": "maxc semantic delete sales --namespace <account_id> --expected-spec-id <spec_id> --json",
|
|
15
24
|
# ── query ──────────────────────────────────────────────────────────────
|
|
16
25
|
"query": (
|
|
17
26
|
'maxc query "SELECT 1"\n'
|
|
@@ -24,6 +33,9 @@ SAMPLES: dict[str, str] = {
|
|
|
24
33
|
"maxc auth login --oauth\n"
|
|
25
34
|
"maxc auth login --from-env --json"
|
|
26
35
|
),
|
|
36
|
+
"auth.login-proxy": (
|
|
37
|
+
"maxc auth login-proxy --project <project> --odps-endpoint <endpoint> --json",
|
|
38
|
+
),
|
|
27
39
|
"auth.login-external": (
|
|
28
40
|
"maxc auth login-external --process-command 'credential-helper --format json'"
|
|
29
41
|
),
|
|
@@ -939,7 +939,11 @@ class MaxCApp:
|
|
|
939
939
|
status="pending",
|
|
940
940
|
data={
|
|
941
941
|
"job_id": job.job_id,
|
|
942
|
-
"safety": build_safety_block(
|
|
942
|
+
"safety": build_safety_block(
|
|
943
|
+
force=force,
|
|
944
|
+
sql=sql,
|
|
945
|
+
effective_hints=job.effective_hints,
|
|
946
|
+
),
|
|
943
947
|
},
|
|
944
948
|
metadata={
|
|
945
949
|
"job_id": job.job_id,
|
|
@@ -985,7 +989,11 @@ class MaxCApp:
|
|
|
985
989
|
status="pending",
|
|
986
990
|
data={
|
|
987
991
|
"job_id": job.job_id,
|
|
988
|
-
"safety": build_safety_block(
|
|
992
|
+
"safety": build_safety_block(
|
|
993
|
+
force=force,
|
|
994
|
+
sql=sql,
|
|
995
|
+
effective_hints=job.effective_hints,
|
|
996
|
+
),
|
|
989
997
|
},
|
|
990
998
|
metadata={
|
|
991
999
|
"job_id": job.job_id,
|
|
@@ -1108,6 +1116,7 @@ class MaxCApp:
|
|
|
1108
1116
|
self.log(command, envelope.status, envelope.metadata)
|
|
1109
1117
|
return envelope
|
|
1110
1118
|
result.extra_metadata.update(execution_metadata)
|
|
1119
|
+
result.effective_hints = dict(job.effective_hints)
|
|
1111
1120
|
for warning in common_warnings:
|
|
1112
1121
|
if warning not in result.warnings:
|
|
1113
1122
|
result.warnings.append(warning)
|
|
@@ -1285,7 +1294,14 @@ class MaxCApp:
|
|
|
1285
1294
|
envelope = Envelope(
|
|
1286
1295
|
command="job.submit",
|
|
1287
1296
|
status="pending",
|
|
1288
|
-
data={
|
|
1297
|
+
data={
|
|
1298
|
+
"job_id": job.job_id,
|
|
1299
|
+
"safety": build_safety_block(
|
|
1300
|
+
force=force,
|
|
1301
|
+
sql=sql,
|
|
1302
|
+
effective_hints=job.effective_hints,
|
|
1303
|
+
),
|
|
1304
|
+
},
|
|
1289
1305
|
metadata={
|
|
1290
1306
|
"job_id": job.job_id,
|
|
1291
1307
|
"project": job.project,
|
|
@@ -2761,6 +2777,19 @@ class MaxCApp:
|
|
|
2761
2777
|
raise ValidationError("--schema must not be empty.")
|
|
2762
2778
|
return target_project, target_schema
|
|
2763
2779
|
|
|
2780
|
+
def semantic_manage(self, operation: str, *, namespace: str, **options) -> 'Envelope':
|
|
2781
|
+
"""Run a remote SemanticSpec workflow, separate from local annotations."""
|
|
2782
|
+
from .semantic_management import SemanticManager
|
|
2783
|
+
|
|
2784
|
+
manager = SemanticManager(self.backend, namespace)
|
|
2785
|
+
data = manager.execute(operation, **options)
|
|
2786
|
+
command = "semantic." + operation
|
|
2787
|
+
metadata = {"namespace": namespace}
|
|
2788
|
+
envelope = Envelope(command=command, status="success", data=data,
|
|
2789
|
+
metadata=metadata, agent_hints=AgentHints(warnings=manager.warnings))
|
|
2790
|
+
self.log(command, envelope.status, metadata)
|
|
2791
|
+
return envelope
|
|
2792
|
+
|
|
2764
2793
|
def semantic_set(
|
|
2765
2794
|
self,
|
|
2766
2795
|
table_name: 'str',
|
|
@@ -4864,6 +4893,37 @@ class MaxCApp:
|
|
|
4864
4893
|
envelope.metadata["continuation_expires_at_unix"] = expires_at
|
|
4865
4894
|
return envelope
|
|
4866
4895
|
|
|
4896
|
+
def auth_login_proxy(
|
|
4897
|
+
self, *, project: str, endpoint: str,
|
|
4898
|
+
region_name: "str | None" = None, tunnel_endpoint: "str | None" = None,
|
|
4899
|
+
no_validate: bool = False, target_config_path: "Path | None" = None,
|
|
4900
|
+
) -> Envelope:
|
|
4901
|
+
"""Save an explicit, credential-free egress authentication configuration."""
|
|
4902
|
+
target_path = target_config_path or default_global_config_path()
|
|
4903
|
+
auth = AuthConfig(provider="proxy", project=project, endpoint=endpoint,
|
|
4904
|
+
region_name=region_name, tunnel_endpoint=tunnel_endpoint)
|
|
4905
|
+
resolved = resolve_auth_connection(self.config, auth_override=auth)
|
|
4906
|
+
if no_validate:
|
|
4907
|
+
identity = {"authenticated": None, "configured": True,
|
|
4908
|
+
"validation_status": "configuration_only", "auth_type": "proxy",
|
|
4909
|
+
"identity_source": "egress_proxy", "project": resolved.project,
|
|
4910
|
+
"endpoint": resolved.endpoint}
|
|
4911
|
+
warnings = ["Configuration saved without remote validation; run agent doctor --online."]
|
|
4912
|
+
else:
|
|
4913
|
+
identity, warnings = self._validate_auth_config(auth)
|
|
4914
|
+
# A failed identity check must leave the previous login untouched.
|
|
4915
|
+
migrate_legacy_session_override(target_path)
|
|
4916
|
+
persist_login_config(target_path, auth=auth)
|
|
4917
|
+
envelope = Envelope(
|
|
4918
|
+
command="auth.login-proxy", status="success",
|
|
4919
|
+
data={"identity": identity,
|
|
4920
|
+
"persistence": {"saved": True, "validated": not no_validate}},
|
|
4921
|
+
metadata={"config_path": str(target_path)},
|
|
4922
|
+
agent_hints=AgentHints(actions=[action("auth.whoami")], warnings=warnings),
|
|
4923
|
+
)
|
|
4924
|
+
self.log("auth.login-proxy", envelope.status, envelope.metadata)
|
|
4925
|
+
return envelope
|
|
4926
|
+
|
|
4867
4927
|
def auth_login_external(
|
|
4868
4928
|
self,
|
|
4869
4929
|
*,
|
|
@@ -5599,6 +5659,8 @@ class MaxCApp:
|
|
|
5599
5659
|
has_creds = bool(effective_settings.get("external_process_command"))
|
|
5600
5660
|
elif provider == "oauth":
|
|
5601
5661
|
has_creds = auth_cfg.oauth.is_configured()
|
|
5662
|
+
elif provider == "proxy":
|
|
5663
|
+
has_creds = True
|
|
5602
5664
|
else:
|
|
5603
5665
|
has_creds = False
|
|
5604
5666
|
|
|
@@ -5756,13 +5818,8 @@ class MaxCApp:
|
|
|
5756
5818
|
failed_check_ids = [
|
|
5757
5819
|
check["id"] for check in checks if check["status"] == "fail"
|
|
5758
5820
|
]
|
|
5759
|
-
command = current_cli_entry_point()
|
|
5760
5821
|
if ready and not online:
|
|
5761
|
-
actions = [
|
|
5762
|
-
id="agent.doctor.online",
|
|
5763
|
-
title="Verify credentials and backend",
|
|
5764
|
-
command=f"{command} agent doctor --online --json",
|
|
5765
|
-
)]
|
|
5822
|
+
actions = [action("agent.doctor", data={"online": True})]
|
|
5766
5823
|
elif ready:
|
|
5767
5824
|
actions = [action("meta.list-tables", data=context_data)]
|
|
5768
5825
|
elif "auth.configured" in failed_check_ids:
|
|
@@ -6557,20 +6614,31 @@ class MaxCApp:
|
|
|
6557
6614
|
|
|
6558
6615
|
# Build actions
|
|
6559
6616
|
qe_actions: list[SuggestedAction] = []
|
|
6617
|
+
write_operations = known_write_operations(result.sql_executed)
|
|
6560
6618
|
if result.tables_used and result.extra_metadata.get("result_kind") != "statement":
|
|
6561
6619
|
qe_actions.append(action("meta.describe", data=data, metadata=metadata))
|
|
6562
6620
|
if result.has_more and next_cursor:
|
|
6563
6621
|
qe_actions.append(action("query.paginate", data=data, metadata=metadata))
|
|
6564
6622
|
elif result.has_more and result.job_id:
|
|
6565
6623
|
qe_actions.append(action("job.result", data=data, metadata=metadata))
|
|
6566
|
-
if dry_run:
|
|
6624
|
+
if dry_run and not write_operations:
|
|
6567
6625
|
qe_actions.append(action("job.submit", data=data, metadata=metadata))
|
|
6626
|
+
elif dry_run and write_operations:
|
|
6627
|
+
warnings.append(
|
|
6628
|
+
"No replay action was generated for mutating SQL. A dry-run does not "
|
|
6629
|
+
"authorize later execution; rerun the exact statement with `--force` "
|
|
6630
|
+
"only after the user confirms that mutation."
|
|
6631
|
+
)
|
|
6568
6632
|
|
|
6569
6633
|
# Add safety block
|
|
6570
6634
|
if command in {"job.wait", "job.result"}:
|
|
6571
6635
|
data["safety"] = build_observational_safety_block(command)
|
|
6572
6636
|
else:
|
|
6573
|
-
data["safety"] = build_safety_block(
|
|
6637
|
+
data["safety"] = build_safety_block(
|
|
6638
|
+
force=force,
|
|
6639
|
+
sql=result.sql_executed,
|
|
6640
|
+
effective_hints=result.effective_hints,
|
|
6641
|
+
)
|
|
6574
6642
|
|
|
6575
6643
|
return Envelope(
|
|
6576
6644
|
command=command,
|
|
@@ -6592,6 +6660,8 @@ class MaxCApp:
|
|
|
6592
6660
|
analysis: 'dict[str, Any]',
|
|
6593
6661
|
force: 'bool' = False,
|
|
6594
6662
|
) -> 'Envelope':
|
|
6663
|
+
analysis = dict(analysis)
|
|
6664
|
+
effective_hints = analysis.pop("_effective_hints", {})
|
|
6595
6665
|
warnings = list(analysis.get("warnings", []))
|
|
6596
6666
|
insights = []
|
|
6597
6667
|
if analysis.get("estimated_input_size_bytes") == 0:
|
|
@@ -6606,14 +6676,26 @@ class MaxCApp:
|
|
|
6606
6676
|
|
|
6607
6677
|
# Build actions
|
|
6608
6678
|
ae_actions: list[SuggestedAction] = []
|
|
6609
|
-
|
|
6610
|
-
|
|
6611
|
-
|
|
6679
|
+
write_operations = known_write_operations(sql)
|
|
6680
|
+
if not write_operations:
|
|
6681
|
+
if command == "query.cost":
|
|
6682
|
+
ae_actions.append(action("query.explain", data=analysis, metadata=metadata))
|
|
6683
|
+
ae_actions.append(action("query", data=analysis, metadata=metadata))
|
|
6684
|
+
else:
|
|
6685
|
+
warnings.append(
|
|
6686
|
+
"No replay action was generated for mutating SQL. Cost or plan analysis "
|
|
6687
|
+
"does not authorize later execution; rerun the exact statement with "
|
|
6688
|
+
"`--force` only after the user confirms that mutation."
|
|
6689
|
+
)
|
|
6612
6690
|
if analysis.get("tables_used"):
|
|
6613
6691
|
ae_actions.append(action("meta.describe", data=analysis, metadata=metadata))
|
|
6614
6692
|
|
|
6615
6693
|
# Add safety block
|
|
6616
|
-
analysis["safety"] = build_safety_block(
|
|
6694
|
+
analysis["safety"] = build_safety_block(
|
|
6695
|
+
force=force,
|
|
6696
|
+
sql=sql,
|
|
6697
|
+
effective_hints=effective_hints,
|
|
6698
|
+
)
|
|
6617
6699
|
|
|
6618
6700
|
return Envelope(
|
|
6619
6701
|
command=command,
|
|
@@ -137,6 +137,8 @@ class ResolvedAuthConnection:
|
|
|
137
137
|
pyodps_catalog.Client instance, or None if the SDK is not
|
|
138
138
|
installed or catalog endpoint is unavailable.
|
|
139
139
|
"""
|
|
140
|
+
if self.provider == "proxy":
|
|
141
|
+
return None
|
|
140
142
|
try:
|
|
141
143
|
from maxcompute_tea_openapi import models as open_api_models
|
|
142
144
|
from pyodps_catalog.client import Client as CatalogClient
|
|
@@ -171,6 +173,8 @@ def auth_settings_available(config: 'MaxCConfig') -> 'bool':
|
|
|
171
173
|
settings, _, _suppressed = resolve_odps_settings(config)
|
|
172
174
|
provider = infer_auth_provider(config, settings)
|
|
173
175
|
try:
|
|
176
|
+
if provider == "proxy":
|
|
177
|
+
return not missing_odps_settings(settings, auth_type="proxy")
|
|
174
178
|
if provider == "sts_token":
|
|
175
179
|
return not missing_odps_settings(settings, auth_type="sts_token")
|
|
176
180
|
if provider == "external":
|
|
@@ -193,6 +197,32 @@ def resolve_auth_connection(
|
|
|
193
197
|
settings, sources, suppressed_env_vars = resolve_odps_settings(config, auth_override=auth_override)
|
|
194
198
|
provider = infer_auth_provider(config, settings, auth_override=auth_override)
|
|
195
199
|
|
|
200
|
+
if provider == "proxy":
|
|
201
|
+
missing = missing_odps_settings(settings, auth_type="proxy")
|
|
202
|
+
if missing:
|
|
203
|
+
raise ValidationError(
|
|
204
|
+
f"Proxy authentication is missing required fields: {', '.join(missing)}.",
|
|
205
|
+
suggestion="Run `auth login-proxy --project <project> --odps-endpoint <endpoint>`.",
|
|
206
|
+
)
|
|
207
|
+
from .proxy_auth import ProxyAccount
|
|
208
|
+
|
|
209
|
+
# Never retain stale credentials or helper configuration in this mode.
|
|
210
|
+
connection_fields = {"provider", "project", "endpoint", "region_name",
|
|
211
|
+
"tunnel_endpoint", "catalog_endpoint"}
|
|
212
|
+
settings = {key: value if key in connection_fields else None
|
|
213
|
+
for key, value in settings.items()}
|
|
214
|
+
return ResolvedAuthConnection(
|
|
215
|
+
auth_type="proxy", provider="proxy",
|
|
216
|
+
project=settings["project"], endpoint=settings["endpoint"],
|
|
217
|
+
region_name=settings.get("region_name"),
|
|
218
|
+
tunnel_endpoint=settings.get("tunnel_endpoint"),
|
|
219
|
+
catalog_endpoint=settings.get("catalog_endpoint"),
|
|
220
|
+
access_id=None, secret_access_key=None, security_token=None,
|
|
221
|
+
token_expires_at=None, identity_source="egress_proxy",
|
|
222
|
+
settings=settings, setting_sources=sources,
|
|
223
|
+
suppressed_env_vars=suppressed_env_vars, account=ProxyAccount(),
|
|
224
|
+
)
|
|
225
|
+
|
|
196
226
|
if provider == "external":
|
|
197
227
|
missing = missing_odps_settings(settings, auth_type="external")
|
|
198
228
|
if missing:
|
|
@@ -329,7 +359,7 @@ def infer_auth_provider(
|
|
|
329
359
|
) -> 'str':
|
|
330
360
|
auth = auth_override or config.auth
|
|
331
361
|
explicit = (auth.provider or settings.get("provider") or "").strip().lower()
|
|
332
|
-
if explicit in {"access_key", "sts_token", "sts", "external", "oauth"}:
|
|
362
|
+
if explicit in {"access_key", "sts_token", "sts", "external", "oauth", "proxy"}:
|
|
333
363
|
if explicit == "sts":
|
|
334
364
|
return "sts_token"
|
|
335
365
|
return explicit
|
|
@@ -326,8 +326,10 @@ class DataMixin:
|
|
|
326
326
|
raise ValidationError(
|
|
327
327
|
f"`{definition.name}` is a view; views are read-only and cannot be loaded via Tunnel.",
|
|
328
328
|
suggestion=(
|
|
329
|
-
"Choose a physical table that supports Tunnel upload
|
|
330
|
-
"
|
|
329
|
+
"Choose a physical table that supports Tunnel upload. If the "
|
|
330
|
+
"user separately authorizes one exact supported SQL write and "
|
|
331
|
+
"its target and effect, submit that statement with --force; "
|
|
332
|
+
"otherwise use an approved bulk data-write workflow."
|
|
331
333
|
),
|
|
332
334
|
)
|
|
333
335
|
partition_columns = {c.name for c in definition.partition_columns}
|
|
@@ -352,8 +354,9 @@ class DataMixin:
|
|
|
352
354
|
raise ValidationError(
|
|
353
355
|
f"Columns {unsupported} have complex types not supported by CSV upload.",
|
|
354
356
|
suggestion=(
|
|
355
|
-
"
|
|
356
|
-
"
|
|
357
|
+
"Use a format and approved data-write workflow that supports "
|
|
358
|
+
"the exact complex types. Do not auto-convert or submit a SQL "
|
|
359
|
+
"write unless the user separately authorizes its target and effect."
|
|
357
360
|
),
|
|
358
361
|
)
|
|
359
362
|
|
|
@@ -781,8 +781,10 @@ class JobMixin(QueryMixin):
|
|
|
781
781
|
return sql.rstrip(";") if sql else None
|
|
782
782
|
|
|
783
783
|
def _safe_logview(self, instance) -> 'str | None':
|
|
784
|
-
"""
|
|
784
|
+
"""Get a LogView URL without exposing its signed query or fragment."""
|
|
785
|
+
from ..utils import sanitize_logview_url
|
|
786
|
+
|
|
785
787
|
try:
|
|
786
|
-
return instance.get_logview_address()
|
|
788
|
+
return sanitize_logview_url(instance.get_logview_address())
|
|
787
789
|
except Exception:
|
|
788
790
|
return None
|
|
@@ -20,6 +20,7 @@ from .data import DataMixin
|
|
|
20
20
|
from .job import JobMixin
|
|
21
21
|
from .meta import MetaMixin
|
|
22
22
|
from .query import _write_operation
|
|
23
|
+
from .semantic import SemanticMixin
|
|
23
24
|
|
|
24
25
|
# When the pyodps instance tunnel is unavailable, pyodps emits a UserWarning
|
|
25
26
|
# and falls back to the CSV result reader, which caps results at 10000 rows.
|
|
@@ -58,6 +59,7 @@ def _summarize_fallback_warning(message: 'str') -> 'str | None':
|
|
|
58
59
|
class OdpsBackend(
|
|
59
60
|
JobMixin, # JobMixin extends QueryMixin
|
|
60
61
|
CatalogMixin,
|
|
62
|
+
SemanticMixin,
|
|
61
63
|
MetaMixin,
|
|
62
64
|
DataMixin,
|
|
63
65
|
AuthMixin,
|