maxc-cli 0.8.1__tar.gz → 0.9.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.8.1 → maxc_cli-0.9.0}/PKG-INFO +14 -2
- {maxc_cli-0.8.1 → maxc_cli-0.9.0}/README.md +13 -1
- {maxc_cli-0.8.1 → maxc_cli-0.9.0}/src/maxc_cli/__init__.py +1 -1
- {maxc_cli-0.8.1 → maxc_cli-0.9.0}/src/maxc_cli/_samples.py +8 -0
- {maxc_cli-0.8.1 → maxc_cli-0.9.0}/src/maxc_cli/app.py +159 -35
- {maxc_cli-0.8.1 → maxc_cli-0.9.0}/src/maxc_cli/backend/job.py +4 -2
- {maxc_cli-0.8.1 → maxc_cli-0.9.0}/src/maxc_cli/backend/meta.py +94 -0
- {maxc_cli-0.8.1 → maxc_cli-0.9.0}/src/maxc_cli/backend/odps.py +18 -4
- {maxc_cli-0.8.1 → maxc_cli-0.9.0}/src/maxc_cli/backend/query.py +0 -1
- {maxc_cli-0.8.1 → maxc_cli-0.9.0}/src/maxc_cli/cli.py +49 -2
- {maxc_cli-0.8.1 → maxc_cli-0.9.0}/src/maxc_cli/exceptions.py +15 -1
- maxc_cli-0.9.0/src/maxc_cli/function_metadata.py +36 -0
- {maxc_cli-0.8.1 → maxc_cli-0.9.0}/src/maxc_cli/helpers.py +17 -1
- {maxc_cli-0.8.1 → maxc_cli-0.9.0}/src/maxc_cli/models.py +10 -1
- {maxc_cli-0.8.1 → maxc_cli-0.9.0}/src/maxc_cli/output.py +21 -4
- {maxc_cli-0.8.1 → maxc_cli-0.9.0}/src/maxc_cli/skills/SKILL.md +14 -2
- {maxc_cli-0.8.1 → maxc_cli-0.9.0}/src/maxc_cli/skills/references/command-patterns.md +7 -0
- maxc_cli-0.9.0/src/maxc_cli/skills/references/registered-functions.md +78 -0
- {maxc_cli-0.8.1 → maxc_cli-0.9.0}/src/maxc_cli/utils.py +42 -7
- {maxc_cli-0.8.1 → maxc_cli-0.9.0}/src/maxc_cli.egg-info/PKG-INFO +14 -2
- {maxc_cli-0.8.1 → maxc_cli-0.9.0}/src/maxc_cli.egg-info/SOURCES.txt +3 -0
- {maxc_cli-0.8.1 → maxc_cli-0.9.0}/tests/test_help_version_e2e.py +8 -0
- {maxc_cli-0.8.1 → maxc_cli-0.9.0}/tests/test_job_improvements.py +52 -0
- {maxc_cli-0.8.1 → maxc_cli-0.9.0}/tests/test_output_action_safety.py +25 -0
- {maxc_cli-0.8.1 → maxc_cli-0.9.0}/tests/test_output_format_contract.py +15 -0
- {maxc_cli-0.8.1 → maxc_cli-0.9.0}/tests/test_query_auto_promote.py +96 -0
- {maxc_cli-0.8.1 → maxc_cli-0.9.0}/tests/test_query_result_csv_fallback.py +26 -2
- maxc_cli-0.9.0/tests/test_registered_functions.py +207 -0
- {maxc_cli-0.8.1 → maxc_cli-0.9.0}/MANIFEST.in +0 -0
- {maxc_cli-0.8.1 → maxc_cli-0.9.0}/pyproject.toml +0 -0
- {maxc_cli-0.8.1 → maxc_cli-0.9.0}/scripts/pyinstaller_entry.py +0 -0
- {maxc_cli-0.8.1 → maxc_cli-0.9.0}/scripts/regression_test.py +0 -0
- {maxc_cli-0.8.1 → maxc_cli-0.9.0}/setup.cfg +0 -0
- {maxc_cli-0.8.1 → maxc_cli-0.9.0}/setup.py +0 -0
- {maxc_cli-0.8.1 → maxc_cli-0.9.0}/src/maxc_cli/__main__.py +0 -0
- {maxc_cli-0.8.1 → maxc_cli-0.9.0}/src/maxc_cli/agent_platforms.py +0 -0
- {maxc_cli-0.8.1 → maxc_cli-0.9.0}/src/maxc_cli/audit.py +0 -0
- {maxc_cli-0.8.1 → maxc_cli-0.9.0}/src/maxc_cli/auth_continuation.py +0 -0
- {maxc_cli-0.8.1 → maxc_cli-0.9.0}/src/maxc_cli/auth_providers.py +0 -0
- {maxc_cli-0.8.1 → maxc_cli-0.9.0}/src/maxc_cli/backend/__init__.py +0 -0
- {maxc_cli-0.8.1 → maxc_cli-0.9.0}/src/maxc_cli/backend/auth.py +0 -0
- {maxc_cli-0.8.1 → maxc_cli-0.9.0}/src/maxc_cli/backend/catalog.py +0 -0
- {maxc_cli-0.8.1 → maxc_cli-0.9.0}/src/maxc_cli/backend/data.py +0 -0
- {maxc_cli-0.8.1 → maxc_cli-0.9.0}/src/maxc_cli/backend/mcp.py +0 -0
- {maxc_cli-0.8.1 → maxc_cli-0.9.0}/src/maxc_cli/backend/semantic.py +0 -0
- {maxc_cli-0.8.1 → maxc_cli-0.9.0}/src/maxc_cli/cache.py +0 -0
- {maxc_cli-0.8.1 → maxc_cli-0.9.0}/src/maxc_cli/catalog_bootstrap.py +0 -0
- {maxc_cli-0.8.1 → maxc_cli-0.9.0}/src/maxc_cli/config.py +0 -0
- {maxc_cli-0.8.1 → maxc_cli-0.9.0}/src/maxc_cli/enterprise_tls.py +0 -0
- {maxc_cli-0.8.1 → maxc_cli-0.9.0}/src/maxc_cli/help_format.py +0 -0
- {maxc_cli-0.8.1 → maxc_cli-0.9.0}/src/maxc_cli/job_ids.py +0 -0
- {maxc_cli-0.8.1 → maxc_cli-0.9.0}/src/maxc_cli/masking.py +0 -0
- {maxc_cli-0.8.1 → maxc_cli-0.9.0}/src/maxc_cli/mcp_serve.py +0 -0
- {maxc_cli-0.8.1 → maxc_cli-0.9.0}/src/maxc_cli/oauth.py +0 -0
- {maxc_cli-0.8.1 → maxc_cli-0.9.0}/src/maxc_cli/odps_runtime.py +0 -0
- {maxc_cli-0.8.1 → maxc_cli-0.9.0}/src/maxc_cli/proxy_auth.py +0 -0
- {maxc_cli-0.8.1 → maxc_cli-0.9.0}/src/maxc_cli/semantic.py +0 -0
- {maxc_cli-0.8.1 → maxc_cli-0.9.0}/src/maxc_cli/semantic_management.py +0 -0
- {maxc_cli-0.8.1 → maxc_cli-0.9.0}/src/maxc_cli/setting_parser.py +0 -0
- {maxc_cli-0.8.1 → maxc_cli-0.9.0}/src/maxc_cli/skills/agents/openai.yaml +0 -0
- {maxc_cli-0.8.1 → maxc_cli-0.9.0}/src/maxc_cli/skills/references/bootstrap-auth.md +0 -0
- {maxc_cli-0.8.1 → maxc_cli-0.9.0}/src/maxc_cli/skills/references/bootstrap-flow.md +0 -0
- {maxc_cli-0.8.1 → maxc_cli-0.9.0}/src/maxc_cli/skills/references/json-output-format.md +0 -0
- {maxc_cli-0.8.1 → maxc_cli-0.9.0}/src/maxc_cli/skills/references/maxcompute-select-guide.md +0 -0
- {maxc_cli-0.8.1 → maxc_cli-0.9.0}/src/maxc_cli/skills/references/maxcompute-sql-notes.md +0 -0
- {maxc_cli-0.8.1 → maxc_cli-0.9.0}/src/maxc_cli/skills/references/partition-guide.md +0 -0
- {maxc_cli-0.8.1 → maxc_cli-0.9.0}/src/maxc_cli/skills/references/red-lines.md +0 -0
- {maxc_cli-0.8.1 → maxc_cli-0.9.0}/src/maxc_cli/skills/references/semantic-packages.md +0 -0
- {maxc_cli-0.8.1 → maxc_cli-0.9.0}/src/maxc_cli/skills/references/setup-install.md +0 -0
- {maxc_cli-0.8.1 → maxc_cli-0.9.0}/src/maxc_cli/skills/references/sql-common-errors.md +0 -0
- {maxc_cli-0.8.1 → maxc_cli-0.9.0}/src/maxc_cli/skills/references/sql-query-patterns.md +0 -0
- {maxc_cli-0.8.1 → maxc_cli-0.9.0}/src/maxc_cli/skills/references/text2sql-principles.md +0 -0
- {maxc_cli-0.8.1 → maxc_cli-0.9.0}/src/maxc_cli/state_permissions.py +0 -0
- {maxc_cli-0.8.1 → maxc_cli-0.9.0}/src/maxc_cli/store.py +0 -0
- {maxc_cli-0.8.1 → maxc_cli-0.9.0}/src/maxc_cli.egg-info/dependency_links.txt +0 -0
- {maxc_cli-0.8.1 → maxc_cli-0.9.0}/src/maxc_cli.egg-info/entry_points.txt +0 -0
- {maxc_cli-0.8.1 → maxc_cli-0.9.0}/src/maxc_cli.egg-info/requires.txt +0 -0
- {maxc_cli-0.8.1 → maxc_cli-0.9.0}/src/maxc_cli.egg-info/top_level.txt +0 -0
- {maxc_cli-0.8.1 → maxc_cli-0.9.0}/tests/test_agent_hints_and_cli.py +0 -0
- {maxc_cli-0.8.1 → maxc_cli-0.9.0}/tests/test_agent_platforms.py +0 -0
- {maxc_cli-0.8.1 → maxc_cli-0.9.0}/tests/test_agent_skill_commands.py +0 -0
- {maxc_cli-0.8.1 → maxc_cli-0.9.0}/tests/test_agent_skill_commands_context.py +0 -0
- {maxc_cli-0.8.1 → maxc_cli-0.9.0}/tests/test_ai_native_contract_regressions.py +0 -0
- {maxc_cli-0.8.1 → maxc_cli-0.9.0}/tests/test_auth_logout.py +0 -0
- {maxc_cli-0.8.1 → maxc_cli-0.9.0}/tests/test_backend_auth.py +0 -0
- {maxc_cli-0.8.1 → maxc_cli-0.9.0}/tests/test_backend_data.py +0 -0
- {maxc_cli-0.8.1 → maxc_cli-0.9.0}/tests/test_backend_data_serialization.py +0 -0
- {maxc_cli-0.8.1 → maxc_cli-0.9.0}/tests/test_backend_mcp.py +0 -0
- {maxc_cli-0.8.1 → maxc_cli-0.9.0}/tests/test_backend_meta.py +0 -0
- {maxc_cli-0.8.1 → maxc_cli-0.9.0}/tests/test_build_release_archive_compat.py +0 -0
- {maxc_cli-0.8.1 → maxc_cli-0.9.0}/tests/test_build_release_script.py +0 -0
- {maxc_cli-0.8.1 → maxc_cli-0.9.0}/tests/test_cache.py +0 -0
- {maxc_cli-0.8.1 → maxc_cli-0.9.0}/tests/test_catalog.py +0 -0
- {maxc_cli-0.8.1 → maxc_cli-0.9.0}/tests/test_catalog_bootstrap.py +0 -0
- {maxc_cli-0.8.1 → maxc_cli-0.9.0}/tests/test_cli_arg_validation.py +0 -0
- {maxc_cli-0.8.1 → maxc_cli-0.9.0}/tests/test_cli_mock.py +0 -0
- {maxc_cli-0.8.1 → maxc_cli-0.9.0}/tests/test_cli_query_parse_and_sanitize.py +0 -0
- {maxc_cli-0.8.1 → maxc_cli-0.9.0}/tests/test_compat.py +0 -0
- {maxc_cli-0.8.1 → maxc_cli-0.9.0}/tests/test_config_atomic_write.py +0 -0
- {maxc_cli-0.8.1 → maxc_cli-0.9.0}/tests/test_connection_bootstrap.py +0 -0
- {maxc_cli-0.8.1 → maxc_cli-0.9.0}/tests/test_e2e_smoke.py +0 -0
- {maxc_cli-0.8.1 → maxc_cli-0.9.0}/tests/test_effective_hints_contract.py +0 -0
- {maxc_cli-0.8.1 → maxc_cli-0.9.0}/tests/test_enterprise_tls.py +0 -0
- {maxc_cli-0.8.1 → maxc_cli-0.9.0}/tests/test_envelope_shape.py +0 -0
- {maxc_cli-0.8.1 → maxc_cli-0.9.0}/tests/test_error_self_correction.py +0 -0
- {maxc_cli-0.8.1 → maxc_cli-0.9.0}/tests/test_error_translation.py +0 -0
- {maxc_cli-0.8.1 → maxc_cli-0.9.0}/tests/test_exit_codes.py +0 -0
- {maxc_cli-0.8.1 → maxc_cli-0.9.0}/tests/test_external_auth.py +0 -0
- {maxc_cli-0.8.1 → maxc_cli-0.9.0}/tests/test_flag_hoist.py +0 -0
- {maxc_cli-0.8.1 → maxc_cli-0.9.0}/tests/test_help_format.py +0 -0
- {maxc_cli-0.8.1 → maxc_cli-0.9.0}/tests/test_helpers.py +0 -0
- {maxc_cli-0.8.1 → maxc_cli-0.9.0}/tests/test_helpers_csv.py +0 -0
- {maxc_cli-0.8.1 → maxc_cli-0.9.0}/tests/test_installer_contracts.py +0 -0
- {maxc_cli-0.8.1 → maxc_cli-0.9.0}/tests/test_integration.py +0 -0
- {maxc_cli-0.8.1 → maxc_cli-0.9.0}/tests/test_integration_real.py +0 -0
- {maxc_cli-0.8.1 → maxc_cli-0.9.0}/tests/test_job_inspection.py +0 -0
- {maxc_cli-0.8.1 → maxc_cli-0.9.0}/tests/test_job_store_durability.py +0 -0
- {maxc_cli-0.8.1 → maxc_cli-0.9.0}/tests/test_kb_commands.py +0 -0
- {maxc_cli-0.8.1 → maxc_cli-0.9.0}/tests/test_manifest_runtime_contract.py +0 -0
- {maxc_cli-0.8.1 → maxc_cli-0.9.0}/tests/test_masking.py +0 -0
- {maxc_cli-0.8.1 → maxc_cli-0.9.0}/tests/test_mcp_serve.py +0 -0
- {maxc_cli-0.8.1 → maxc_cli-0.9.0}/tests/test_meta_schema_and_partition_cols.py +0 -0
- {maxc_cli-0.8.1 → maxc_cli-0.9.0}/tests/test_oauth.py +0 -0
- {maxc_cli-0.8.1 → maxc_cli-0.9.0}/tests/test_odps_runtime.py +0 -0
- {maxc_cli-0.8.1 → maxc_cli-0.9.0}/tests/test_packaging_metadata.py +0 -0
- {maxc_cli-0.8.1 → maxc_cli-0.9.0}/tests/test_phase1_improvements.py +0 -0
- {maxc_cli-0.8.1 → maxc_cli-0.9.0}/tests/test_proxy_auth.py +0 -0
- {maxc_cli-0.8.1 → maxc_cli-0.9.0}/tests/test_pyinstaller_bundle.py +0 -0
- {maxc_cli-0.8.1 → maxc_cli-0.9.0}/tests/test_python39_compat.py +0 -0
- {maxc_cli-0.8.1 → maxc_cli-0.9.0}/tests/test_semantic_management.py +0 -0
- {maxc_cli-0.8.1 → maxc_cli-0.9.0}/tests/test_semantic_scope.py +0 -0
- {maxc_cli-0.8.1 → maxc_cli-0.9.0}/tests/test_semantic_transport.py +0 -0
- {maxc_cli-0.8.1 → maxc_cli-0.9.0}/tests/test_setting_parser.py +0 -0
- {maxc_cli-0.8.1 → maxc_cli-0.9.0}/tests/test_skill_cli_consistency.py +0 -0
- {maxc_cli-0.8.1 → maxc_cli-0.9.0}/tests/test_skill_eval.py +0 -0
- {maxc_cli-0.8.1 → maxc_cli-0.9.0}/tests/test_skill_renderer.py +0 -0
- {maxc_cli-0.8.1 → maxc_cli-0.9.0}/tests/test_startup_imports.py +0 -0
- {maxc_cli-0.8.1 → maxc_cli-0.9.0}/tests/test_state_permissions.py +0 -0
- {maxc_cli-0.8.1 → maxc_cli-0.9.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.9.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
|
|
@@ -70,7 +70,7 @@ maxc agent doctor --online --json
|
|
|
70
70
|
|------|------|------|
|
|
71
71
|
| **query** | `query [run]`, `query cost`, `query explain` | SQL 执行、成本估算、执行计划 |
|
|
72
72
|
| **job** | `submit`, `status`, `wait`, `result`, `cancel`, `diagnose`, `list` | 异步任务全生命周期 |
|
|
73
|
-
| **meta** | `list-tables`, `describe`, `search`, `search-columns`, `partitions`, `latest-partition`, `freshness`, `list-projects`, `list-schemas`, `semantic set/get/clear/list-missing` | 元数据发现与语义管理 |
|
|
73
|
+
| **meta** | `list-tables`, `describe`, `list-functions`, `describe-function`, `search`, `search-columns`, `partitions`, `latest-partition`, `freshness`, `list-projects`, `list-schemas`, `semantic set/get/clear/list-missing` | 元数据发现与语义管理 |
|
|
74
74
|
| **data** | `sample`, `profile`, `upload`, `download` | 数据采样、画像与 CSV/TSV 传输 |
|
|
75
75
|
| **auth** | `login`, `login-external`, `logout`, `whoami`, `can-i` | 认证与权限 |
|
|
76
76
|
| **session** | `set`, `show`, `unset` | 项目/Schema 切换 |
|
|
@@ -81,6 +81,18 @@ maxc agent doctor --online --json
|
|
|
81
81
|
使用 `--json`;CSV/NDJSON 行流和 `job wait --stream` 生命周期流是明确例外,
|
|
82
82
|
不会为每条记录重复封装 Envelope。
|
|
83
83
|
|
|
84
|
+
0.9.0 起支持已注册函数的目录与详情查询:
|
|
85
|
+
|
|
86
|
+
```bash
|
|
87
|
+
aliyun maxc meta list-functions --project <project> --prefix <prefix> --limit 20 --json
|
|
88
|
+
aliyun maxc meta describe-function <alias> --project <project> --json
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
三层项目可加 `--schema`;详情参数使用裸函数别名。列表有界分页,详情保留
|
|
92
|
+
类名和原始资源引用,不下载资源内容,也不推断签名或 Python 版本。
|
|
93
|
+
按作者文档确定调用契约后,复用现有 SQL 成本、执行和作业命令引用函数。
|
|
94
|
+
具体流程见 [已注册函数](src/maxc_cli/skills/references/registered-functions.md)。
|
|
95
|
+
|
|
84
96
|
## Agent 集成
|
|
85
97
|
|
|
86
98
|
### 方式 1:公共 Skill(主路径)
|
|
@@ -48,7 +48,7 @@ maxc agent doctor --online --json
|
|
|
48
48
|
|------|------|------|
|
|
49
49
|
| **query** | `query [run]`, `query cost`, `query explain` | SQL 执行、成本估算、执行计划 |
|
|
50
50
|
| **job** | `submit`, `status`, `wait`, `result`, `cancel`, `diagnose`, `list` | 异步任务全生命周期 |
|
|
51
|
-
| **meta** | `list-tables`, `describe`, `search`, `search-columns`, `partitions`, `latest-partition`, `freshness`, `list-projects`, `list-schemas`, `semantic set/get/clear/list-missing` | 元数据发现与语义管理 |
|
|
51
|
+
| **meta** | `list-tables`, `describe`, `list-functions`, `describe-function`, `search`, `search-columns`, `partitions`, `latest-partition`, `freshness`, `list-projects`, `list-schemas`, `semantic set/get/clear/list-missing` | 元数据发现与语义管理 |
|
|
52
52
|
| **data** | `sample`, `profile`, `upload`, `download` | 数据采样、画像与 CSV/TSV 传输 |
|
|
53
53
|
| **auth** | `login`, `login-external`, `logout`, `whoami`, `can-i` | 认证与权限 |
|
|
54
54
|
| **session** | `set`, `show`, `unset` | 项目/Schema 切换 |
|
|
@@ -59,6 +59,18 @@ maxc agent doctor --online --json
|
|
|
59
59
|
使用 `--json`;CSV/NDJSON 行流和 `job wait --stream` 生命周期流是明确例外,
|
|
60
60
|
不会为每条记录重复封装 Envelope。
|
|
61
61
|
|
|
62
|
+
0.9.0 起支持已注册函数的目录与详情查询:
|
|
63
|
+
|
|
64
|
+
```bash
|
|
65
|
+
aliyun maxc meta list-functions --project <project> --prefix <prefix> --limit 20 --json
|
|
66
|
+
aliyun maxc meta describe-function <alias> --project <project> --json
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
三层项目可加 `--schema`;详情参数使用裸函数别名。列表有界分页,详情保留
|
|
70
|
+
类名和原始资源引用,不下载资源内容,也不推断签名或 Python 版本。
|
|
71
|
+
按作者文档确定调用契约后,复用现有 SQL 成本、执行和作业命令引用函数。
|
|
72
|
+
具体流程见 [已注册函数](src/maxc_cli/skills/references/registered-functions.md)。
|
|
73
|
+
|
|
62
74
|
## Agent 集成
|
|
63
75
|
|
|
64
76
|
### 方式 1:公共 Skill(主路径)
|
|
@@ -85,6 +85,14 @@ SAMPLES: dict[str, str] = {
|
|
|
85
85
|
"maxc meta describe default.orders --project my_proj\n"
|
|
86
86
|
"maxc meta describe default.orders --project my_proj --full --json"
|
|
87
87
|
),
|
|
88
|
+
"meta.list-functions": (
|
|
89
|
+
"maxc meta list-functions --project my_proj --prefix normalize_ --limit 20 --json\n"
|
|
90
|
+
"maxc meta list-functions --project my_proj --schema default --limit 20 --json"
|
|
91
|
+
),
|
|
92
|
+
"meta.describe-function": (
|
|
93
|
+
"maxc meta describe-function normalize_phone --project my_proj --json\n"
|
|
94
|
+
"maxc meta describe-function normalize_phone --project my_proj --schema default --json"
|
|
95
|
+
),
|
|
88
96
|
"meta.search": (
|
|
89
97
|
"maxc meta search orders\n"
|
|
90
98
|
"maxc meta search user --project my_proj --json"
|
|
@@ -44,6 +44,7 @@ from .exceptions import (
|
|
|
44
44
|
TwoTierNamespaceError,
|
|
45
45
|
ValidationError,
|
|
46
46
|
)
|
|
47
|
+
from .function_metadata import function_cursor, function_offset
|
|
47
48
|
from .helpers import (
|
|
48
49
|
build_odps_identity_payload,
|
|
49
50
|
build_task_summary,
|
|
@@ -866,13 +867,22 @@ class MaxCApp:
|
|
|
866
867
|
session["job_id"],
|
|
867
868
|
project=session.get("project") or None,
|
|
868
869
|
)
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
870
|
+
try:
|
|
871
|
+
result = self.backend.fetch_job_result(
|
|
872
|
+
resolved.instance_id,
|
|
873
|
+
project=resolved.project,
|
|
874
|
+
max_rows=max_rows,
|
|
875
|
+
offset=offset,
|
|
876
|
+
session_context=resolved.session_context,
|
|
877
|
+
)
|
|
878
|
+
except Exception as exc:
|
|
879
|
+
error = exc if isinstance(exc, MaxCError) else translate_odps_error(exc)
|
|
880
|
+
envelope = self._job_result_fetch_failure(
|
|
881
|
+
command, resolved, None, error.to_payload(),
|
|
882
|
+
max_rows=max_rows, cursor=cursor,
|
|
883
|
+
)
|
|
884
|
+
self.log(command, envelope.status, envelope.metadata)
|
|
885
|
+
return envelope
|
|
876
886
|
envelope = self._build_query_envelope(
|
|
877
887
|
command=command,
|
|
878
888
|
result=result,
|
|
@@ -1098,25 +1108,13 @@ class MaxCApp:
|
|
|
1098
1108
|
except Exception as exc:
|
|
1099
1109
|
fetch_error = translate_odps_error(exc).to_payload()
|
|
1100
1110
|
if fetch_error is not None:
|
|
1101
|
-
envelope =
|
|
1102
|
-
command=
|
|
1103
|
-
status="failure",
|
|
1104
|
-
data=None,
|
|
1105
|
-
error=fetch_error,
|
|
1111
|
+
envelope = self._job_result_fetch_failure(
|
|
1112
|
+
command, resolved, job_info, fetch_error, max_rows=max_rows,
|
|
1106
1113
|
metadata={
|
|
1107
|
-
"job_id": resolved.external_job_id,
|
|
1108
|
-
"project": resolved.project,
|
|
1109
|
-
"logview": job_info.logview,
|
|
1110
1114
|
"sql_executed": sql,
|
|
1111
1115
|
**execution_metadata,
|
|
1112
1116
|
},
|
|
1113
|
-
|
|
1114
|
-
actions=[
|
|
1115
|
-
action("job.result", data={"job_id": resolved.external_job_id}, metadata={"job_id": resolved.external_job_id, "project": resolved.project, "sql_executed": sql}),
|
|
1116
|
-
action("job.status", data={"job_id": resolved.external_job_id}, metadata={"job_id": resolved.external_job_id, "project": resolved.project}),
|
|
1117
|
-
],
|
|
1118
|
-
warnings=common_warnings,
|
|
1119
|
-
),
|
|
1117
|
+
warnings=common_warnings,
|
|
1120
1118
|
)
|
|
1121
1119
|
if idempotency_key:
|
|
1122
1120
|
envelope.metadata["idempotency_key"] = idempotency_key
|
|
@@ -1458,12 +1456,20 @@ class MaxCApp:
|
|
|
1458
1456
|
]
|
|
1459
1457
|
self.log("job.wait", envelope.status, envelope.metadata)
|
|
1460
1458
|
return envelope, events
|
|
1461
|
-
|
|
1462
|
-
|
|
1463
|
-
|
|
1464
|
-
|
|
1465
|
-
|
|
1466
|
-
|
|
1459
|
+
try:
|
|
1460
|
+
result = self.backend.fetch_job_result(
|
|
1461
|
+
resolved.instance_id,
|
|
1462
|
+
project=resolved.project,
|
|
1463
|
+
max_rows=100,
|
|
1464
|
+
session_context=resolved.session_context,
|
|
1465
|
+
)
|
|
1466
|
+
except Exception as exc:
|
|
1467
|
+
error = exc if isinstance(exc, MaxCError) else translate_odps_error(exc)
|
|
1468
|
+
envelope = self._job_result_fetch_failure(
|
|
1469
|
+
"job.wait", resolved, after, error.to_payload(), max_rows=100,
|
|
1470
|
+
)
|
|
1471
|
+
self.log("job.wait", envelope.status, envelope.metadata)
|
|
1472
|
+
return envelope, []
|
|
1467
1473
|
envelope = self._build_query_envelope(
|
|
1468
1474
|
command="job.wait",
|
|
1469
1475
|
result=result,
|
|
@@ -1561,13 +1567,22 @@ class MaxCApp:
|
|
|
1561
1567
|
)
|
|
1562
1568
|
self.log("job.result", envelope.status, envelope.metadata)
|
|
1563
1569
|
return envelope
|
|
1564
|
-
|
|
1565
|
-
|
|
1566
|
-
|
|
1567
|
-
|
|
1568
|
-
|
|
1569
|
-
|
|
1570
|
-
|
|
1570
|
+
try:
|
|
1571
|
+
result = self.backend.fetch_job_result(
|
|
1572
|
+
resolved.instance_id,
|
|
1573
|
+
project=resolved.project,
|
|
1574
|
+
max_rows=max_rows,
|
|
1575
|
+
offset=offset,
|
|
1576
|
+
session_context=resolved.session_context,
|
|
1577
|
+
)
|
|
1578
|
+
except Exception as exc:
|
|
1579
|
+
error = exc if isinstance(exc, MaxCError) else translate_odps_error(exc)
|
|
1580
|
+
envelope = self._job_result_fetch_failure(
|
|
1581
|
+
"job.result", resolved, info, error.to_payload(),
|
|
1582
|
+
max_rows=max_rows, cursor=cursor,
|
|
1583
|
+
)
|
|
1584
|
+
self.log("job.result", envelope.status, envelope.metadata)
|
|
1585
|
+
return envelope
|
|
1571
1586
|
envelope = self._build_query_envelope(
|
|
1572
1587
|
command="job.result",
|
|
1573
1588
|
result=result,
|
|
@@ -1669,6 +1684,48 @@ class MaxCApp:
|
|
|
1669
1684
|
self.log("job.result", envelope.status, envelope.metadata)
|
|
1670
1685
|
return envelope
|
|
1671
1686
|
|
|
1687
|
+
def _job_result_fetch_failure(
|
|
1688
|
+
self,
|
|
1689
|
+
command: 'str',
|
|
1690
|
+
resolved: '_ResolvedExternalJobId',
|
|
1691
|
+
info: 'JobInfo | None',
|
|
1692
|
+
error: 'ErrorPayload',
|
|
1693
|
+
*,
|
|
1694
|
+
max_rows: 'int',
|
|
1695
|
+
cursor: 'str | None' = None,
|
|
1696
|
+
metadata: 'dict[str, Any] | None' = None,
|
|
1697
|
+
warnings: 'list[str] | None' = None,
|
|
1698
|
+
) -> 'Envelope':
|
|
1699
|
+
"""Keep the existing job and page observable when reading fails."""
|
|
1700
|
+
scope = {
|
|
1701
|
+
"job_id": resolved.external_job_id,
|
|
1702
|
+
"project": resolved.project,
|
|
1703
|
+
"logview": info.logview if info else error.logview,
|
|
1704
|
+
**({
|
|
1705
|
+
"job_status": info.status,
|
|
1706
|
+
"submitted_at": info.submitted_at,
|
|
1707
|
+
"completed_at": info.completed_at,
|
|
1708
|
+
} if info else {}),
|
|
1709
|
+
**(metadata or {}),
|
|
1710
|
+
}
|
|
1711
|
+
retry = action("job.result", data={"max_rows": max_rows, "cursor": cursor}, metadata=scope)
|
|
1712
|
+
error.instance_id = resolved.external_job_id
|
|
1713
|
+
error.logview = scope["logview"]
|
|
1714
|
+
error.recovery_steps = [
|
|
1715
|
+
"Retry reading the existing job result: " + retry.command,
|
|
1716
|
+
"Do not resubmit the SQL to recover a result-reader failure.",
|
|
1717
|
+
]
|
|
1718
|
+
return Envelope(
|
|
1719
|
+
command=command,
|
|
1720
|
+
status="failure",
|
|
1721
|
+
error=error,
|
|
1722
|
+
metadata=scope,
|
|
1723
|
+
agent_hints=AgentHints(
|
|
1724
|
+
actions=[retry, action("job.diagnose", metadata=scope)],
|
|
1725
|
+
warnings=warnings or [],
|
|
1726
|
+
),
|
|
1727
|
+
)
|
|
1728
|
+
|
|
1672
1729
|
def _bound_job_result_cursor(
|
|
1673
1730
|
self,
|
|
1674
1731
|
cursor: 'str | None',
|
|
@@ -1967,6 +2024,73 @@ class MaxCApp:
|
|
|
1967
2024
|
self.log("job.list", envelope.status, envelope.metadata)
|
|
1968
2025
|
return envelope
|
|
1969
2026
|
|
|
2027
|
+
def meta_list_functions(
|
|
2028
|
+
self,
|
|
2029
|
+
*,
|
|
2030
|
+
schema: 'str | None' = None,
|
|
2031
|
+
project: 'str | None' = None,
|
|
2032
|
+
prefix: 'str | None' = None,
|
|
2033
|
+
limit: int = 50,
|
|
2034
|
+
cursor: 'str | None' = None,
|
|
2035
|
+
) -> Envelope:
|
|
2036
|
+
if type(limit) is not int or not 1 <= limit <= 1000:
|
|
2037
|
+
raise ValidationError("Function list limit must be between 1 and 1000.")
|
|
2038
|
+
target_project = project or self.config.default_project
|
|
2039
|
+
effective_schema = schema or self.config.default_schema
|
|
2040
|
+
offset = function_offset(cursor, project=target_project, schema=effective_schema, prefix=prefix)
|
|
2041
|
+
rows, has_more = self.backend.list_functions(
|
|
2042
|
+
project=target_project, schema=effective_schema, prefix=prefix, limit=limit, offset=offset,
|
|
2043
|
+
)
|
|
2044
|
+
data = {
|
|
2045
|
+
"functions": [dict(row, project=target_project, schema_name=effective_schema) for row in rows],
|
|
2046
|
+
"pagination": {
|
|
2047
|
+
"returned_count": len(rows),
|
|
2048
|
+
"has_more": has_more,
|
|
2049
|
+
"limit": limit,
|
|
2050
|
+
"offset": offset,
|
|
2051
|
+
"next_cursor": function_cursor(
|
|
2052
|
+
offset + len(rows), project=target_project, schema=effective_schema, prefix=prefix,
|
|
2053
|
+
) if has_more else None,
|
|
2054
|
+
},
|
|
2055
|
+
}
|
|
2056
|
+
metadata = {"project": target_project, "schema": effective_schema, "prefix": prefix}
|
|
2057
|
+
envelope = Envelope(
|
|
2058
|
+
command="meta.list-functions", status="success", data=data, metadata=metadata,
|
|
2059
|
+
agent_hints=AgentHints(warnings=[
|
|
2060
|
+
"Catalog pages are live and not a snapshot; registrations can change between calls.",
|
|
2061
|
+
"Function aliases are metadata names, not SQL-qualified references. Verify namespace settings before constructing SQL.",
|
|
2062
|
+
]),
|
|
2063
|
+
)
|
|
2064
|
+
self.log(envelope.command, envelope.status, metadata)
|
|
2065
|
+
return envelope
|
|
2066
|
+
|
|
2067
|
+
def meta_describe_function(
|
|
2068
|
+
self,
|
|
2069
|
+
function_name: str,
|
|
2070
|
+
*,
|
|
2071
|
+
schema: 'str | None' = None,
|
|
2072
|
+
project: 'str | None' = None,
|
|
2073
|
+
) -> Envelope:
|
|
2074
|
+
if not function_name or any(char.isspace() or char in ".:/\\" for char in function_name):
|
|
2075
|
+
raise ValidationError(
|
|
2076
|
+
"Pass a bare registered function alias.",
|
|
2077
|
+
suggestion="Select the function's project and schema with --project and --schema.",
|
|
2078
|
+
)
|
|
2079
|
+
target_project = project or self.config.default_project
|
|
2080
|
+
effective_schema = schema or self.config.default_schema
|
|
2081
|
+
detail = self.backend.describe_function(function_name, project=target_project, schema=effective_schema)
|
|
2082
|
+
detail = dict(detail, project=target_project, schema_name=effective_schema)
|
|
2083
|
+
metadata = {"project": target_project, "schema": effective_schema}
|
|
2084
|
+
envelope = Envelope(
|
|
2085
|
+
command="meta.describe-function", status="success", data={"function": detail}, metadata=metadata,
|
|
2086
|
+
agent_hints=AgentHints(warnings=[
|
|
2087
|
+
"Registration metadata does not establish the input/output signature or runtime version. Use the function owner's documentation or verified user context; do not infer them from class or resource names.",
|
|
2088
|
+
"Metadata Read and SQL Execute are separate permissions. A failed describe is not proof that an already-known function cannot be executed.",
|
|
2089
|
+
]),
|
|
2090
|
+
)
|
|
2091
|
+
self.log(envelope.command, envelope.status, metadata)
|
|
2092
|
+
return envelope
|
|
2093
|
+
|
|
1970
2094
|
def meta_list_tables(
|
|
1971
2095
|
self,
|
|
1972
2096
|
*,
|
|
@@ -846,10 +846,12 @@ class JobMixin(QueryMixin):
|
|
|
846
846
|
return sql.rstrip(";") if sql else None
|
|
847
847
|
|
|
848
848
|
def _safe_logview(self, instance) -> 'str | None':
|
|
849
|
-
"""Get a LogView URL
|
|
849
|
+
"""Get a usable SDK LogView URL for the caller, without URL userinfo."""
|
|
850
850
|
from ..utils import sanitize_logview_url
|
|
851
851
|
|
|
852
852
|
try:
|
|
853
|
-
return sanitize_logview_url(
|
|
853
|
+
return sanitize_logview_url(
|
|
854
|
+
instance.get_logview_address(), include_access_token=True,
|
|
855
|
+
)
|
|
854
856
|
except Exception:
|
|
855
857
|
return None
|
|
@@ -20,6 +20,100 @@ from ..helpers import (
|
|
|
20
20
|
class MetaMixin:
|
|
21
21
|
"""Mixin providing metadata methods."""
|
|
22
22
|
|
|
23
|
+
def list_functions(
|
|
24
|
+
self,
|
|
25
|
+
*,
|
|
26
|
+
project: 'str | None' = None,
|
|
27
|
+
schema: 'str | None' = None,
|
|
28
|
+
prefix: 'str | None' = None,
|
|
29
|
+
limit: int = 50,
|
|
30
|
+
offset: int = 0,
|
|
31
|
+
) -> 'tuple[list[dict[str, Any]], bool]':
|
|
32
|
+
"""Read a bounded catalog window without per-function metadata GETs.
|
|
33
|
+
|
|
34
|
+
Args:
|
|
35
|
+
project: Function project, or the configured project.
|
|
36
|
+
schema: Explicit function schema, when applicable.
|
|
37
|
+
prefix: Server-side alias prefix filter.
|
|
38
|
+
limit: Maximum returned functions.
|
|
39
|
+
offset: Number of earlier matching catalog entries to skip.
|
|
40
|
+
|
|
41
|
+
Returns:
|
|
42
|
+
Parsed collection rows and whether another matching entry exists.
|
|
43
|
+
"""
|
|
44
|
+
try:
|
|
45
|
+
iterator = iter(self.client.list_functions(
|
|
46
|
+
project=project or self.project, schema=schema, prefix=prefix,
|
|
47
|
+
))
|
|
48
|
+
window = list(islice(iterator, offset, offset + limit + 1))
|
|
49
|
+
# PyODPS models lazily reload missing fields. Read only parsed
|
|
50
|
+
# collection fields here so List does not also require Read.
|
|
51
|
+
rows = [{
|
|
52
|
+
"function_name": object.__getattribute__(item, "name"),
|
|
53
|
+
"class_type": object.__getattribute__(item, "class_type"),
|
|
54
|
+
"owner": object.__getattribute__(item, "_owner"),
|
|
55
|
+
} for item in window[:limit]]
|
|
56
|
+
return rows, len(window) > limit
|
|
57
|
+
except Exception as exc:
|
|
58
|
+
raise translate_odps_error(exc, "list_functions") from exc
|
|
59
|
+
|
|
60
|
+
def describe_function(
|
|
61
|
+
self,
|
|
62
|
+
function_name: str,
|
|
63
|
+
*,
|
|
64
|
+
project: 'str | None' = None,
|
|
65
|
+
schema: 'str | None' = None,
|
|
66
|
+
) -> 'dict[str, Any]':
|
|
67
|
+
"""Read registration metadata without loading resource contents.
|
|
68
|
+
|
|
69
|
+
Args:
|
|
70
|
+
function_name: Bare registered function alias.
|
|
71
|
+
project: Function project, or the configured project.
|
|
72
|
+
schema: Explicit function schema, when applicable.
|
|
73
|
+
|
|
74
|
+
Returns:
|
|
75
|
+
Registration metadata with exact resource references and unknown
|
|
76
|
+
signature/runtime fields. Implementation code is excluded.
|
|
77
|
+
"""
|
|
78
|
+
try:
|
|
79
|
+
function = self.client.get_function(
|
|
80
|
+
function_name, project=project or self.project, schema=schema,
|
|
81
|
+
)
|
|
82
|
+
# get_function is lazy: reload explicitly so missing aliases fail
|
|
83
|
+
# even when the SDK already knows the requested name.
|
|
84
|
+
function.reload()
|
|
85
|
+
|
|
86
|
+
def read(key: str) -> Any:
|
|
87
|
+
try:
|
|
88
|
+
return object.__getattribute__(function, key)
|
|
89
|
+
except AttributeError:
|
|
90
|
+
# Older supported PyODPS releases omit optional flags.
|
|
91
|
+
return None
|
|
92
|
+
|
|
93
|
+
return {
|
|
94
|
+
"function_name": read("name"),
|
|
95
|
+
"class_type": read("class_type"),
|
|
96
|
+
"resource_names": list(read("_resources") or []),
|
|
97
|
+
"owner": read("_owner"),
|
|
98
|
+
"creation_time": _dt_to_iso(read("creation_time")),
|
|
99
|
+
"program_language": read("program_language"),
|
|
100
|
+
"is_sql_function": self._function_boolean(read("is_sql_function")),
|
|
101
|
+
"is_embedded_function": self._function_boolean(read("is_embedded_function")),
|
|
102
|
+
"file_name": read("file_name"),
|
|
103
|
+
"signature": None,
|
|
104
|
+
"runtime_version": None,
|
|
105
|
+
}
|
|
106
|
+
except Exception as exc:
|
|
107
|
+
raise translate_odps_error(exc, "describe_function") from exc
|
|
108
|
+
|
|
109
|
+
@staticmethod
|
|
110
|
+
def _function_boolean(value: Any) -> 'bool | None':
|
|
111
|
+
if value is True or str(value).lower() == "true":
|
|
112
|
+
return True
|
|
113
|
+
if value is False or str(value).lower() == "false":
|
|
114
|
+
return False
|
|
115
|
+
return None
|
|
116
|
+
|
|
23
117
|
def list_tables(
|
|
24
118
|
self,
|
|
25
119
|
*,
|
|
@@ -152,7 +152,17 @@ class OdpsBackend(
|
|
|
152
152
|
# timeout), the column metadata is parsed lazily from the
|
|
153
153
|
# CSV header during the first __next__ call — so we can
|
|
154
154
|
# only inspect it after iteration begins.
|
|
155
|
-
|
|
155
|
+
iterator = iter(reader)
|
|
156
|
+
skipped = sum(1 for _ in islice(iterator, offset))
|
|
157
|
+
records = list(islice(iterator, max_rows))
|
|
158
|
+
count = getattr(reader, "count", None)
|
|
159
|
+
# CSV fallback readers expose no count. Count the
|
|
160
|
+
# remaining accessible records without retaining
|
|
161
|
+
# them so a page is not mistaken for the full result.
|
|
162
|
+
accessible_count = (
|
|
163
|
+
skipped + len(records) + sum(1 for _ in iterator)
|
|
164
|
+
if count is None else int(count)
|
|
165
|
+
)
|
|
156
166
|
|
|
157
167
|
reader_schema = getattr(reader, "schema", None)
|
|
158
168
|
if reader_schema is not None and hasattr(reader_schema, "columns"):
|
|
@@ -181,7 +191,7 @@ class OdpsBackend(
|
|
|
181
191
|
record_to_dict(column_names, record.values)
|
|
182
192
|
for record in records
|
|
183
193
|
]
|
|
184
|
-
total_rows =
|
|
194
|
+
total_rows = accessible_count
|
|
185
195
|
except Exception as reader_exc:
|
|
186
196
|
# Some SHOW results (e.g. multi-column SHOW INDEXES
|
|
187
197
|
# col_names) contain unquoted commas that make
|
|
@@ -197,7 +207,7 @@ class OdpsBackend(
|
|
|
197
207
|
rows = all_raw_rows[offset : offset + max_rows]
|
|
198
208
|
schema = [{"name": name, "type": "string", "comment": ""}
|
|
199
209
|
for name in _RAW_RESULT_COLUMNS]
|
|
200
|
-
total_rows = len(
|
|
210
|
+
total_rows = len(all_raw_rows)
|
|
201
211
|
|
|
202
212
|
for w in captured:
|
|
203
213
|
msg_text = str(w.message)
|
|
@@ -210,7 +220,10 @@ class OdpsBackend(
|
|
|
210
220
|
warnings.formatwarning(w.message, w.category, w.filename, w.lineno)
|
|
211
221
|
)
|
|
212
222
|
except Exception as exc:
|
|
213
|
-
|
|
223
|
+
error = translate_odps_error(exc)
|
|
224
|
+
error.instance_id = instance.id
|
|
225
|
+
error.logview = self._safe_logview(instance)
|
|
226
|
+
raise error from exc
|
|
214
227
|
|
|
215
228
|
if degraded_reason is not None:
|
|
216
229
|
fallback_warnings.append(
|
|
@@ -223,6 +236,7 @@ class OdpsBackend(
|
|
|
223
236
|
returned_rows = len(rows)
|
|
224
237
|
has_more = total_rows > (offset + returned_rows)
|
|
225
238
|
extra_metadata["current_offset"] = offset
|
|
239
|
+
extra_metadata["logview"] = self._safe_logview(instance)
|
|
226
240
|
if degraded_reason is not None:
|
|
227
241
|
extra_metadata["result_kind"] = "raw_task_result"
|
|
228
242
|
if resultless_statement:
|
|
@@ -506,7 +506,6 @@ class QueryMixin:
|
|
|
506
506
|
submitted_at=now_utc_iso(),
|
|
507
507
|
updated_at=now_utc_iso(),
|
|
508
508
|
logview=self._safe_logview(instance),
|
|
509
|
-
warnings=["The MaxCompute instance has been submitted; use job.status or job.wait to track it."],
|
|
510
509
|
session_task_name=session_task_name,
|
|
511
510
|
session_subquery_id=session_subquery_id,
|
|
512
511
|
session_project_name=getattr(getattr(instance, "project", None), "name", None),
|
|
@@ -455,6 +455,22 @@ def build_parser() -> argparse.ArgumentParser:
|
|
|
455
455
|
meta_describe.add_argument("--full", action="store_true", help="Show full column list (default is summary mode)")
|
|
456
456
|
meta_describe.set_defaults(handler=_handle_meta_describe)
|
|
457
457
|
|
|
458
|
+
meta_functions = _make_parser(meta_subparsers, "list-functions", "meta.list-functions", help="List registered functions")
|
|
459
|
+
meta_functions.add_argument("--project", help="Target MaxCompute project")
|
|
460
|
+
meta_functions.add_argument("--schema", help="Schema name (overrides session default)")
|
|
461
|
+
meta_functions.add_argument("--prefix", help="Function alias prefix filter")
|
|
462
|
+
meta_functions.add_argument("--limit", type=positive_int, default=50, help="Maximum functions to return (1-1000; default 50)")
|
|
463
|
+
meta_functions.add_argument("--cursor", help="Scope-bound cursor returned by the previous function list")
|
|
464
|
+
meta_functions.add_argument("--json", action="store_true", help="Output as JSON envelope")
|
|
465
|
+
meta_functions.set_defaults(handler=_handle_meta_list_functions)
|
|
466
|
+
|
|
467
|
+
meta_function = _make_parser(meta_subparsers, "describe-function", "meta.describe-function", help="Describe a registered function")
|
|
468
|
+
meta_function.add_argument("function_name", help="Bare registered alias; select scope with --project / --schema")
|
|
469
|
+
meta_function.add_argument("--project", help="Target MaxCompute project")
|
|
470
|
+
meta_function.add_argument("--schema", help="Schema name (overrides session default)")
|
|
471
|
+
meta_function.add_argument("--json", action="store_true", help="Output as JSON envelope")
|
|
472
|
+
meta_function.set_defaults(handler=_handle_meta_describe_function)
|
|
473
|
+
|
|
458
474
|
meta_search = _make_parser(meta_subparsers, "search", "meta.search", help="Search tables")
|
|
459
475
|
meta_search.add_argument("keyword", help="Search keyword")
|
|
460
476
|
meta_search.add_argument("--schema", help="Schema name (overrides session default)")
|
|
@@ -1786,7 +1802,7 @@ def run(
|
|
|
1786
1802
|
if getattr(exc, "logview", None):
|
|
1787
1803
|
from .utils import sanitize_logview_url
|
|
1788
1804
|
|
|
1789
|
-
stderr.write(f" LogView: {sanitize_logview_url(exc.logview)}\n")
|
|
1805
|
+
stderr.write(f" LogView: {sanitize_logview_url(exc.logview, include_access_token=True)}\n")
|
|
1790
1806
|
return exc.exit_code
|
|
1791
1807
|
except Exception as exc:
|
|
1792
1808
|
error_payload = ErrorPayload(
|
|
@@ -2121,6 +2137,18 @@ def _handle_meta_list_tables(app: MaxCApp, args: argparse.Namespace, stdout: Tex
|
|
|
2121
2137
|
_emit_envelope(envelope, args=args, stdout=stdout, default_format="table")
|
|
2122
2138
|
|
|
2123
2139
|
|
|
2140
|
+
def _handle_meta_list_functions(app: MaxCApp, args: argparse.Namespace, stdout: TextIO) -> None:
|
|
2141
|
+
envelope = app.meta_list_functions(
|
|
2142
|
+
project=args.project, schema=args.schema, prefix=args.prefix, limit=args.limit, cursor=args.cursor,
|
|
2143
|
+
)
|
|
2144
|
+
_emit_envelope(envelope, args=args, stdout=stdout, default_format="table")
|
|
2145
|
+
|
|
2146
|
+
|
|
2147
|
+
def _handle_meta_describe_function(app: MaxCApp, args: argparse.Namespace, stdout: TextIO) -> None:
|
|
2148
|
+
envelope = app.meta_describe_function(args.function_name, project=args.project, schema=args.schema)
|
|
2149
|
+
_emit_envelope(envelope, args=args, stdout=stdout, default_format="table")
|
|
2150
|
+
|
|
2151
|
+
|
|
2124
2152
|
def _handle_meta_describe(app: MaxCApp, args: argparse.Namespace, stdout: TextIO) -> None:
|
|
2125
2153
|
# When --json is used, always return full schema (agents need all columns)
|
|
2126
2154
|
full = args.full or _is_json_mode(args)
|
|
@@ -2966,6 +2994,8 @@ def _manifest_effects(command: str) -> list[dict[str, Any]]:
|
|
|
2966
2994
|
),
|
|
2967
2995
|
]
|
|
2968
2996
|
effects_by_command: dict[str, list[dict[str, Any]]] = {
|
|
2997
|
+
"meta.list-functions": [_manifest_effect("remote", "read", "maxcompute_function_catalog")],
|
|
2998
|
+
"meta.describe-function": [_manifest_effect("remote", "read", "maxcompute_function_metadata")],
|
|
2969
2999
|
"agent.context": [
|
|
2970
3000
|
_manifest_effect("local", "read", "config_and_runtime_readiness"),
|
|
2971
3001
|
],
|
|
@@ -4446,7 +4476,7 @@ def _render_human(envelope: Envelope) -> str:
|
|
|
4446
4476
|
if logview:
|
|
4447
4477
|
from .utils import sanitize_logview_url
|
|
4448
4478
|
|
|
4449
|
-
details["logview"] = sanitize_logview_url(logview)
|
|
4479
|
+
details["logview"] = sanitize_logview_url(logview, include_access_token=True)
|
|
4450
4480
|
if error.context:
|
|
4451
4481
|
details["context"] = error.context
|
|
4452
4482
|
sections.append(render_key_values(details))
|
|
@@ -4491,11 +4521,20 @@ def _render_human(envelope: Envelope) -> str:
|
|
|
4491
4521
|
)
|
|
4492
4522
|
return "\n\n".join(section for section in sections if section)
|
|
4493
4523
|
|
|
4524
|
+
if envelope.status == "pending" and (command == "query" or command.startswith("job.")):
|
|
4525
|
+
from .output import render_markdown
|
|
4526
|
+
|
|
4527
|
+
return render_markdown(envelope)
|
|
4528
|
+
|
|
4494
4529
|
if command == "query":
|
|
4530
|
+
from .utils import sanitize_logview_url
|
|
4531
|
+
|
|
4495
4532
|
rows = data.get("rows", [])
|
|
4496
4533
|
summary = render_key_values(
|
|
4497
4534
|
{
|
|
4498
4535
|
"status": envelope.status,
|
|
4536
|
+
"job_id": metadata.get("job_id"),
|
|
4537
|
+
"logview": sanitize_logview_url(metadata.get("logview"), include_access_token=True),
|
|
4499
4538
|
"project": metadata.get("project"),
|
|
4500
4539
|
"elapsed_ms": metadata.get("elapsed_ms"),
|
|
4501
4540
|
"returned_rows": data.get("returned_rows"),
|
|
@@ -4518,6 +4557,12 @@ def _render_human(envelope: Envelope) -> str:
|
|
|
4518
4557
|
if command == "meta.list-tables":
|
|
4519
4558
|
return render_table(data.get("tables", []))
|
|
4520
4559
|
|
|
4560
|
+
if command == "meta.list-functions":
|
|
4561
|
+
return render_table(data.get("functions", []))
|
|
4562
|
+
|
|
4563
|
+
if command == "meta.describe-function":
|
|
4564
|
+
return render_key_values(data.get("function", {}))
|
|
4565
|
+
|
|
4521
4566
|
if command == "meta.describe":
|
|
4522
4567
|
# Render schema/partition_columns as nested sub-tables instead of
|
|
4523
4568
|
# JSON-stringifying them into a single cell.
|
|
@@ -4584,6 +4629,7 @@ _RECORD_COLLECTION_KEYS: dict[str, tuple[str, str]] = {
|
|
|
4584
4629
|
"meta.list-projects": ("projects", "project"),
|
|
4585
4630
|
"meta.list-schemas": ("schemas", "schema"),
|
|
4586
4631
|
"meta.list-tables": ("tables", "table"),
|
|
4632
|
+
"meta.list-functions": ("functions", "function"),
|
|
4587
4633
|
"meta.partitions": ("partitions", "partition"),
|
|
4588
4634
|
"meta.search": ("matches", "match"),
|
|
4589
4635
|
"meta.search-columns": ("matches", "match"),
|
|
@@ -4596,6 +4642,7 @@ _RECORD_DEFAULT_COLUMNS: dict[str, list[str]] = {
|
|
|
4596
4642
|
"job.list": ["job_id", "status", "progress", "project", "submitted_at"],
|
|
4597
4643
|
"meta.list-projects": ["name"],
|
|
4598
4644
|
"meta.list-schemas": ["name"],
|
|
4645
|
+
"meta.list-functions": ["function_name", "project", "schema_name", "class_type", "owner"],
|
|
4599
4646
|
"meta.list-tables": [
|
|
4600
4647
|
"table_name",
|
|
4601
4648
|
"schema_name",
|