maxc-cli 0.1.0__tar.gz → 0.1.1__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.
Files changed (49) hide show
  1. {maxc_cli-0.1.0 → maxc_cli-0.1.1}/PKG-INFO +1 -1
  2. {maxc_cli-0.1.0 → maxc_cli-0.1.1}/setup.py +1 -1
  3. maxc_cli-0.1.1/skills/use-maxc-cli/.DS_Store +0 -0
  4. {maxc_cli-0.1.0 → maxc_cli-0.1.1}/skills/use-maxc-cli/SKILL.md +76 -4
  5. {maxc_cli-0.1.0 → maxc_cli-0.1.1}/skills/use-maxc-cli/references/setup-install.md +7 -3
  6. {maxc_cli-0.1.0 → maxc_cli-0.1.1}/src/maxc_cli/__init__.py +1 -1
  7. {maxc_cli-0.1.0 → maxc_cli-0.1.1}/src/maxc_cli/app.py +36 -31
  8. {maxc_cli-0.1.0 → maxc_cli-0.1.1}/src/maxc_cli/backend/meta.py +15 -9
  9. {maxc_cli-0.1.0 → maxc_cli-0.1.1}/src/maxc_cli/cli.py +9 -3
  10. {maxc_cli-0.1.0 → maxc_cli-0.1.1}/src/maxc_cli.egg-info/PKG-INFO +1 -1
  11. {maxc_cli-0.1.0 → maxc_cli-0.1.1}/src/maxc_cli.egg-info/SOURCES.txt +1 -0
  12. {maxc_cli-0.1.0 → maxc_cli-0.1.1}/tests/test_agent_hints_and_cli.py +6 -10
  13. {maxc_cli-0.1.0 → maxc_cli-0.1.1}/tests/test_cli_mock.py +141 -0
  14. {maxc_cli-0.1.0 → maxc_cli-0.1.1}/MANIFEST.in +0 -0
  15. {maxc_cli-0.1.0 → maxc_cli-0.1.1}/README.md +0 -0
  16. {maxc_cli-0.1.0 → maxc_cli-0.1.1}/pyproject.toml +0 -0
  17. {maxc_cli-0.1.0 → maxc_cli-0.1.1}/scripts/sync_codex_skill.py +0 -0
  18. {maxc_cli-0.1.0 → maxc_cli-0.1.1}/setup.cfg +0 -0
  19. {maxc_cli-0.1.0 → maxc_cli-0.1.1}/skills/use-maxc-cli/agents/openai.yaml +0 -0
  20. {maxc_cli-0.1.0 → maxc_cli-0.1.1}/skills/use-maxc-cli/references/bootstrap-auth.md +0 -0
  21. {maxc_cli-0.1.0 → maxc_cli-0.1.1}/skills/use-maxc-cli/references/command-patterns.md +0 -0
  22. {maxc_cli-0.1.0 → maxc_cli-0.1.1}/src/maxc_cli/__main__.py +0 -0
  23. {maxc_cli-0.1.0 → maxc_cli-0.1.1}/src/maxc_cli/audit.py +0 -0
  24. {maxc_cli-0.1.0 → maxc_cli-0.1.1}/src/maxc_cli/auth_providers.py +0 -0
  25. {maxc_cli-0.1.0 → maxc_cli-0.1.1}/src/maxc_cli/backend/__init__.py +0 -0
  26. {maxc_cli-0.1.0 → maxc_cli-0.1.1}/src/maxc_cli/backend/auth.py +0 -0
  27. {maxc_cli-0.1.0 → maxc_cli-0.1.1}/src/maxc_cli/backend/data.py +0 -0
  28. {maxc_cli-0.1.0 → maxc_cli-0.1.1}/src/maxc_cli/backend/job.py +0 -0
  29. {maxc_cli-0.1.0 → maxc_cli-0.1.1}/src/maxc_cli/backend/odps.py +0 -0
  30. {maxc_cli-0.1.0 → maxc_cli-0.1.1}/src/maxc_cli/backend/query.py +0 -0
  31. {maxc_cli-0.1.0 → maxc_cli-0.1.1}/src/maxc_cli/cache.py +0 -0
  32. {maxc_cli-0.1.0 → maxc_cli-0.1.1}/src/maxc_cli/config.py +0 -0
  33. {maxc_cli-0.1.0 → maxc_cli-0.1.1}/src/maxc_cli/exceptions.py +0 -0
  34. {maxc_cli-0.1.0 → maxc_cli-0.1.1}/src/maxc_cli/helpers.py +0 -0
  35. {maxc_cli-0.1.0 → maxc_cli-0.1.1}/src/maxc_cli/models.py +0 -0
  36. {maxc_cli-0.1.0 → maxc_cli-0.1.1}/src/maxc_cli/output.py +0 -0
  37. {maxc_cli-0.1.0 → maxc_cli-0.1.1}/src/maxc_cli/store.py +0 -0
  38. {maxc_cli-0.1.0 → maxc_cli-0.1.1}/src/maxc_cli/utils.py +0 -0
  39. {maxc_cli-0.1.0 → maxc_cli-0.1.1}/src/maxc_cli.egg-info/dependency_links.txt +0 -0
  40. {maxc_cli-0.1.0 → maxc_cli-0.1.1}/src/maxc_cli.egg-info/entry_points.txt +0 -0
  41. {maxc_cli-0.1.0 → maxc_cli-0.1.1}/src/maxc_cli.egg-info/requires.txt +0 -0
  42. {maxc_cli-0.1.0 → maxc_cli-0.1.1}/src/maxc_cli.egg-info/top_level.txt +0 -0
  43. {maxc_cli-0.1.0 → maxc_cli-0.1.1}/tests/test_cache.py +0 -0
  44. {maxc_cli-0.1.0 → maxc_cli-0.1.1}/tests/test_compat.py +0 -0
  45. {maxc_cli-0.1.0 → maxc_cli-0.1.1}/tests/test_e2e_smoke.py +0 -0
  46. {maxc_cli-0.1.0 → maxc_cli-0.1.1}/tests/test_integration.py +0 -0
  47. {maxc_cli-0.1.0 → maxc_cli-0.1.1}/tests/test_integration_real.py +0 -0
  48. {maxc_cli-0.1.0 → maxc_cli-0.1.1}/tests/test_job_improvements.py +0 -0
  49. {maxc_cli-0.1.0 → maxc_cli-0.1.1}/tests/test_query_auto_promote.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: maxc-cli
3
- Version: 0.1.0
3
+ Version: 0.1.1
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.8
@@ -9,7 +9,7 @@ README = ROOT / "README.md"
9
9
 
10
10
  setup(
11
11
  name="maxc-cli",
12
- version="0.1.0",
12
+ version="0.1.1",
13
13
  description="Agent-native MaxCompute CLI for external coding agents",
14
14
  long_description=README.read_text(encoding="utf-8"),
15
15
  long_description_content_type="text/markdown",
@@ -63,7 +63,7 @@ Then follow the corresponding section in [references/bootstrap-auth.md](referenc
63
63
  - `configured=false` → no auth set up → **ask which method** (see Bootstrap Flow above).
64
64
  - `configured=true, validation_status=failed` → config exists but remote check failed → inspect warnings, then fix or re-login.
65
65
  3. Read [references/bootstrap-auth.md](references/bootstrap-auth.md) for auth paths.
66
- 4. If `meta list-tables --json` returns `cache_miss`, run `cache build --json` first.
66
+ 4. `meta list-tables --json` falls back to live queries on cache miss. Run `cache build --json` to speed up repeat queries.
67
67
  5. Read [references/command-patterns.md](references/command-patterns.md) for command syntax and output shapes.
68
68
 
69
69
  ## Working Rules
@@ -75,7 +75,8 @@ Then follow the corresponding section in [references/bootstrap-auth.md](referenc
75
75
  - Trust runtime help and actual command output over stale snippets.
76
76
  - Never install or upgrade Python without explicit user confirmation.
77
77
  - Prefer `auth login` over hand-editing `~/.maxc/config.yaml`.
78
- - `meta list-tables` is cache-backed; returns `cache_miss` on cold cache.
78
+ - `meta list-tables` is cache-backed; falls back to live backend query on cache miss.
79
+ - Most meta commands support `--schema` to override the session default (list-tables, search, search-columns).
79
80
  - `session set/show/unset` are local-only — no authenticated backend required.
80
81
  - `agent context` is a fast local config summary; does not enumerate tables.
81
82
  - Use normalized `data` shapes: `auth whoami` → `data.identity`, `query`/`job result` → `data.result`, `meta describe` → `data.table`, `data sample` → `data.sample`.
@@ -87,7 +88,7 @@ Then follow the corresponding section in [references/bootstrap-auth.md](referenc
87
88
  |---------|-----------------|
88
89
  | Using `auth login --from-env` without checking env vars exist | Run `auth whoami --json` first; only use `--from-env` when env vars are confirmed set |
89
90
  | Hand-editing `~/.maxc/config.yaml` | Use `auth login` |
90
- | Calling `meta list-tables` on a cold cache | Run `cache build --json` first |
91
+ | Calling `meta list-tables` on a cold cache | Tables are fetched live on cache miss; `cache build` improves speed for repeat queries |
91
92
  | Inventing endpoints | Only use endpoints the user provided or that exist in current config |
92
93
  | Using `job wait --stream` and expecting a JSON envelope | `--stream` emits NDJSON; use plain `job wait --json` for envelope |
93
94
  | Running a query without checking cost first | Use `query cost` before large queries; use `--cost-check` to set auto-abort threshold |
@@ -146,6 +147,60 @@ maxc session unset --json
146
147
 
147
148
  Session overrides are stored in `~/.maxc/session_override.yaml` and take priority over config files and env vars for project/schema only.
148
149
 
150
+ ## Schema Operations
151
+
152
+ For projects with 3-tier namespace (project.schema.table):
153
+
154
+ ```bash
155
+ # List available schemas
156
+ maxc meta list-schemas --json
157
+
158
+ # List tables in a specific schema (two approaches)
159
+ maxc meta list-tables --schema california_schools --json # one-shot
160
+ maxc session set --schema california_schools --json # sticky session
161
+
162
+ # Search within a schema
163
+ maxc meta search school --schema california_schools --json
164
+ maxc meta search-columns county --schema california_schools --json
165
+
166
+ # Build cache for a specific schema
167
+ maxc cache build --schema california_schools --json
168
+
169
+ # Describe a table (use schema.table_name format)
170
+ maxc meta describe california_schools.frpm --json
171
+
172
+ # Reset to default schema
173
+ maxc session unset --json
174
+ ```
175
+
176
+ When `--schema` is given, it overrides `session set --schema`. When neither is set, the project default schema is used.
177
+
178
+ ## Cache Mechanism
179
+
180
+ The metadata cache accelerates `list-tables`, `search`, `search-columns`, and `describe`.
181
+
182
+ - **How it works**: `cache build` fetches all table metadata from MaxCompute and stores it in a local SQLite DB (`~/.maxc/cache/cache.db`). Subsequent meta commands read from cache first.
183
+ - **Cache key**: `(project, schema_name, table_name)` — schema is part of the key, so different schemas have independent caches.
184
+ - **Cache miss behavior**: `list-tables` and `search` fall back to live backend queries on cache miss. No manual cache build is required, but caching speeds up repeated queries.
185
+ - **When to rebuild**: After schema changes, new tables, or when cache is stale. Check with `cache status --json`.
186
+
187
+ ```bash
188
+ maxc cache build --json # build for current project/schema
189
+ maxc cache build --schema my_schema --json # build for specific schema
190
+ maxc cache status --json # check cache freshness
191
+ maxc cache clear --json # wipe and rebuild
192
+ ```
193
+
194
+ ## Known Limitations
195
+
196
+ | Feature | Status | Detail |
197
+ |---------|--------|--------|
198
+ | `meta lineage` | Placeholder | Returns `supported=false`; MaxCompute lineage API not yet integrated |
199
+ | `list-tables` pagination | Not implemented | CLI-side `--cursor` is offset token, not server-side cursor |
200
+ | `diff data` | Snapshot compare | Keyed snapshot compare, not exhaustive diff |
201
+ | `auth login` | Plaintext YAML | AccessKey stored in `~/.maxc/config.yaml` (file permissions 0600) |
202
+ | Write operations | Read-only | CLI enforces SELECT-only; DDL/DML not supported |
203
+
149
204
  ## Cost Control
150
205
 
151
206
  Before running large queries, always estimate cost first:
@@ -210,9 +265,26 @@ maxc diff data prod_table staging_table \
210
265
  --json
211
266
  ```
212
267
 
268
+ ## Troubleshooting
269
+
270
+ | Symptom | Cause | Fix |
271
+ |---------|-------|-----|
272
+ | `list-tables` returns empty but tables exist | Wrong schema or no tables in default schema | Use `--schema <name>` or `session set --schema` |
273
+ | `search` returns no matches | Keyword not in table/column names or descriptions | Try broader keywords; check with `list-tables --schema` first |
274
+ | `cache build` reports 0 tables | Schema not specified for non-default schemas | Add `--schema <name>` |
275
+ | `describe` fails with NOT_FOUND | Table in a different schema | Use `schema.table_name` format or set session schema |
276
+ | Commands hang or timeout | Network/endpoint issue | Check `auth whoami --json` for endpoint; verify connectivity |
277
+
278
+ When all else fails, verify with raw pyodps:
279
+ ```python
280
+ from odps import ODPS
281
+ o = ODPS(access_id, secret_key, project, endpoint)
282
+ list(o.list_tables(schema='<schema_name>'))
283
+ ```
284
+
213
285
  ## Command Families
214
286
 
215
- - Bootstrap: `python3 --version`, `pip install maxc-cli`, `python3 -m maxc_cli --help`
287
+ - Bootstrap: `python3 --version`, `pip install maxc-cli -i http://yum.tbsite.net/aliyun-pypi/simple --trusted-host yum.tbsite.net`, `python3 -m maxc_cli --help`
216
288
  - Auth and session: `auth whoami`, `auth login`, `auth can-i`, `session set/show/unset`
217
289
  - Metadata and data: `meta list-tables`, `meta describe`, `meta search`, `meta search-columns`, `meta latest-partition`, `meta freshness`, `meta partitions`, `meta list-projects`, `meta list-schemas`, `data sample`, `data profile`
218
290
  - Query and jobs: `query`, `query cost`, `query explain`, `job submit/status/wait/result/diagnose/cancel/list`
@@ -44,16 +44,20 @@ Do not install or upgrade Python proactively. First tell the user why the curren
44
44
 
45
45
  ## Step 2: Install `maxc-cli`
46
46
 
47
- Install with pip:
47
+ `maxc-cli` 发布在内部 PyPI 仓库,安装时需要指定 index URL:
48
48
 
49
49
  ```bash
50
- python3 -m pip install --upgrade maxc-cli
50
+ python3 -m pip install --upgrade maxc-cli \
51
+ -i http://yum.tbsite.net/aliyun-pypi/simple \
52
+ --trusted-host yum.tbsite.net
51
53
  ```
52
54
 
53
55
  If the environment requires a user-local install:
54
56
 
55
57
  ```bash
56
- python3 -m pip install --user --upgrade maxc-cli
58
+ python3 -m pip install --user --upgrade maxc-cli \
59
+ -i http://yum.tbsite.net/aliyun-pypi/simple \
60
+ --trusted-host yum.tbsite.net
57
61
  ```
58
62
 
59
63
  Verify install with either the console script or module path:
@@ -2,4 +2,4 @@
2
2
 
3
3
  __all__ = ["__version__"]
4
4
 
5
- __version__ = "0.1.0"
5
+ __version__ = "0.1.1"
@@ -820,12 +820,16 @@ class MaxCApp:
820
820
  self.log("job.list", envelope.status, envelope.metadata)
821
821
  return envelope
822
822
 
823
- def meta_list_tables(self) -> 'Envelope':
823
+ def meta_list_tables(self, *, schema: 'str | None' = None) -> 'Envelope':
824
824
  started = monotonic()
825
-
825
+ effective_schema = schema or self.config.default_schema
826
+
826
827
  # Try to get from cache first
827
- cached_tables = self.cache.get_all_cached_tables(self.config.default_project)
828
-
828
+ cached_tables = self.cache.get_all_cached_tables(
829
+ self.config.default_project,
830
+ schema_name=effective_schema,
831
+ )
832
+
829
833
  if cached_tables:
830
834
  # Use cached data (returns list of dicts)
831
835
  tables = cached_tables
@@ -845,22 +849,20 @@ class MaxCApp:
845
849
  for table in tables
846
850
  ]
847
851
  else:
848
- # No cache - return guidance to build cache first
849
- return Envelope(
850
- command="meta.list-tables",
851
- status="cache_miss",
852
- data={"tables": [], "total": 0},
853
- metadata=self._cache_metadata(
854
- project=self.config.default_project,
855
- source="none",
856
- query_time_ms=int((monotonic() - started) * 1000),
857
- ),
858
- agent_hints=AgentHints(
859
- next_actions=["cache.build"],
860
- insights=["No metadata cache found for this project. Building the cache first will significantly improve performance."],
861
- warnings=["Cache miss: Run `maxc cache build` to populate the metadata cache."],
862
- ),
863
- )
852
+ # Cache miss fall back to live backend query
853
+ live_tables = self.backend.list_tables(schema=effective_schema)
854
+ source = "backend"
855
+ rows = [
856
+ {
857
+ "table_name": t.name,
858
+ "table_type": t.table_type or "TABLE",
859
+ "size_bytes": t.size_bytes,
860
+ "owner": t.owner,
861
+ "description": t.description,
862
+ "partition_columns": [c.name for c in (t.partition_columns or [])],
863
+ }
864
+ for t in live_tables
865
+ ]
864
866
 
865
867
  metadata = self._cache_metadata(
866
868
  project=self.config.default_project,
@@ -981,14 +983,17 @@ class MaxCApp:
981
983
  self.log("meta.describe", envelope.status, envelope.metadata)
982
984
  return envelope
983
985
 
984
- def meta_search(self, keyword: 'str') -> 'Envelope':
986
+ def meta_search(self, keyword: 'str', *, schema: 'str | None' = None) -> 'Envelope':
985
987
  started = monotonic()
986
- cached_tables = self.cache.get_all_cached_tables(self.config.default_project)
988
+ effective_schema = schema or self.config.default_schema
989
+ cached_tables = self.cache.get_all_cached_tables(
990
+ self.config.default_project, schema_name=effective_schema,
991
+ )
987
992
  if cached_tables:
988
993
  matches = self._search_in_cache(keyword, cached_tables)
989
994
  source = "cache"
990
995
  else:
991
- matches = self.backend.search_tables(keyword)
996
+ matches = self.backend.search_tables(keyword, schema=effective_schema)
992
997
  source = "live"
993
998
  envelope = Envelope(
994
999
  command="meta.search",
@@ -1007,14 +1012,17 @@ class MaxCApp:
1007
1012
  self.log("meta.search", envelope.status, envelope.metadata)
1008
1013
  return envelope
1009
1014
 
1010
- def meta_search_columns(self, keyword: 'str') -> 'Envelope':
1015
+ def meta_search_columns(self, keyword: 'str', *, schema: 'str | None' = None) -> 'Envelope':
1011
1016
  started = monotonic()
1012
- cached_tables = self.cache.get_all_cached_tables(self.config.default_project)
1017
+ effective_schema = schema or self.config.default_schema
1018
+ cached_tables = self.cache.get_all_cached_tables(
1019
+ self.config.default_project, schema_name=effective_schema,
1020
+ )
1013
1021
  if cached_tables:
1014
1022
  matches = self._search_columns_in_cache(keyword, cached_tables)
1015
1023
  source = "cache"
1016
1024
  else:
1017
- matches = self.backend.search_columns(keyword)
1025
+ matches = self.backend.search_columns(keyword, schema=effective_schema)
1018
1026
  source = "live"
1019
1027
  envelope = Envelope(
1020
1028
  command="meta.search-columns",
@@ -1361,11 +1369,8 @@ class MaxCApp:
1361
1369
  }
1362
1370
  )
1363
1371
 
1364
- all_tables = self.backend.list_tables()
1365
- if schema_name:
1366
- tables = all_tables
1367
- else:
1368
- tables = all_tables
1372
+ all_tables = self.backend.list_tables(schema=schema_name)
1373
+ tables = all_tables
1369
1374
 
1370
1375
  if progress_callback is not None:
1371
1376
  progress_callback(
@@ -17,11 +17,14 @@ from ..helpers import (
17
17
  class MetaMixin:
18
18
  """Mixin providing metadata methods."""
19
19
 
20
- def list_tables(self) -> 'list[TableDefinition]':
21
- """List tables in the current project."""
20
+ def list_tables(self, *, schema: 'str | None' = None) -> 'list[TableDefinition]':
21
+ """List tables in the current project, optionally filtered by schema."""
22
22
  tables: 'list[TableDefinition]' = []
23
+ kwargs: 'dict[str, Any]' = {"project": self.project}
24
+ if schema:
25
+ kwargs["schema"] = schema
23
26
  try:
24
- for table in self.client.list_tables(project=self.project):
27
+ for table in self.client.list_tables(**kwargs):
25
28
  tables.append(self._table_stub(table))
26
29
  except Exception as exc:
27
30
  raise translate_odps_error(exc) from exc
@@ -37,11 +40,11 @@ class MetaMixin:
37
40
  definition.sample_rows = sample_rows
38
41
  return definition
39
42
 
40
- def search_tables(self, keyword: 'str') -> 'list[dict[str, Any]]':
43
+ def search_tables(self, keyword: 'str', *, schema: 'str | None' = None) -> 'list[dict[str, Any]]':
41
44
  """Search tables by keyword."""
42
45
  tokens = [item.lower() for item in keyword.split() if item.strip()] or [keyword.lower()]
43
46
  matches: 'list[dict[str, Any]]' = []
44
- for table in self.list_tables():
47
+ for table in self.list_tables(schema=schema):
45
48
  score = 0
46
49
  searchable = f"{table.name} {table.description}".lower()
47
50
  matched_columns: 'list[str]' = []
@@ -65,11 +68,11 @@ class MetaMixin:
65
68
  )
66
69
  return sorted(matches, key=lambda item: (-item["score"], item["table_name"]))
67
70
 
68
- def search_columns(self, keyword: 'str') -> 'list[dict[str, Any]]':
71
+ def search_columns(self, keyword: 'str', *, schema: 'str | None' = None) -> 'list[dict[str, Any]]':
69
72
  """Search columns by keyword."""
70
73
  tokens = [item.lower() for item in keyword.split() if item.strip()] or [keyword.lower()]
71
74
  matches: 'list[dict[str, Any]]' = []
72
- for table in self.list_tables():
75
+ for table in self.list_tables(schema=schema):
73
76
  for column in table.columns:
74
77
  score = 0
75
78
  text = f"{column.name} {column.comment}".lower()
@@ -183,10 +186,13 @@ class MetaMixin:
183
186
 
184
187
  # Private methods for metadata handling
185
188
 
186
- def _get_table(self, table_name: 'str', *, project: 'str | None' = None):
189
+ def _get_table(self, table_name: 'str', *, project: 'str | None' = None, schema: 'str | None' = None):
187
190
  """Get ODPS table by name."""
191
+ kwargs: 'dict[str, Any]' = {"project": project or self.project}
192
+ if schema:
193
+ kwargs["schema"] = schema
188
194
  try:
189
- return self.client.get_table(table_name, project=project or self.project)
195
+ return self.client.get_table(table_name, **kwargs)
190
196
  except Exception as exc:
191
197
  raise translate_odps_error(exc) from exc
192
198
 
@@ -121,6 +121,7 @@ def build_parser() -> 'argparse.ArgumentParser':
121
121
  meta_subparsers = _add_required_subparsers(meta_parser, dest="meta_command")
122
122
 
123
123
  meta_list = meta_subparsers.add_parser("list-tables", help="List tables")
124
+ meta_list.add_argument("--schema", help="Schema name (overrides session default)")
124
125
  meta_list.add_argument("--json", action="store_true", help="Output as JSON envelope")
125
126
  meta_list.set_defaults(handler=_handle_meta_list_tables)
126
127
 
@@ -132,11 +133,13 @@ def build_parser() -> 'argparse.ArgumentParser':
132
133
 
133
134
  meta_search = meta_subparsers.add_parser("search", help="Search tables")
134
135
  meta_search.add_argument("keyword", help="Search keyword")
136
+ meta_search.add_argument("--schema", help="Schema name (overrides session default)")
135
137
  meta_search.add_argument("--json", action="store_true", help="Output as JSON envelope")
136
138
  meta_search.set_defaults(handler=_handle_meta_search)
137
139
 
138
140
  meta_search_columns = meta_subparsers.add_parser("search-columns", help="Search columns")
139
141
  meta_search_columns.add_argument("keyword", help="Search keyword")
142
+ meta_search_columns.add_argument("--schema", help="Schema name (overrides session default)")
140
143
  meta_search_columns.add_argument("--json", action="store_true", help="Output as JSON envelope")
141
144
  meta_search_columns.set_defaults(handler=_handle_meta_search_columns)
142
145
 
@@ -542,7 +545,8 @@ def _handle_job_list(app: 'MaxCApp', args: 'argparse.Namespace', stdout: 'TextIO
542
545
 
543
546
 
544
547
  def _handle_meta_list_tables(app: 'MaxCApp', args: 'argparse.Namespace', stdout: 'TextIO') -> 'None':
545
- envelope = app.meta_list_tables()
548
+ schema = getattr(args, "schema", None)
549
+ envelope = app.meta_list_tables(schema=schema)
546
550
  _emit_envelope(envelope, args=args, stdout=stdout, default_format="table")
547
551
 
548
552
 
@@ -552,12 +556,14 @@ def _handle_meta_describe(app: 'MaxCApp', args: 'argparse.Namespace', stdout: 'T
552
556
 
553
557
 
554
558
  def _handle_meta_search(app: 'MaxCApp', args: 'argparse.Namespace', stdout: 'TextIO') -> 'None':
555
- envelope = app.meta_search(args.keyword)
559
+ schema = getattr(args, "schema", None)
560
+ envelope = app.meta_search(args.keyword, schema=schema)
556
561
  _emit_envelope(envelope, args=args, stdout=stdout, default_format="table")
557
562
 
558
563
 
559
564
  def _handle_meta_search_columns(app: 'MaxCApp', args: 'argparse.Namespace', stdout: 'TextIO') -> 'None':
560
- envelope = app.meta_search_columns(args.keyword)
565
+ schema = getattr(args, "schema", None)
566
+ envelope = app.meta_search_columns(args.keyword, schema=schema)
561
567
  _emit_envelope(envelope, args=args, stdout=stdout, default_format="table")
562
568
 
563
569
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: maxc-cli
3
- Version: 0.1.0
3
+ Version: 0.1.1
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.8
@@ -3,6 +3,7 @@ README.md
3
3
  pyproject.toml
4
4
  setup.py
5
5
  scripts/sync_codex_skill.py
6
+ skills/use-maxc-cli/.DS_Store
6
7
  skills/use-maxc-cli/SKILL.md
7
8
  skills/use-maxc-cli/agents/openai.yaml
8
9
  skills/use-maxc-cli/references/bootstrap-auth.md
@@ -169,7 +169,7 @@ def _table(name: 'str' = "sales.orders") -> 'TableDefinition':
169
169
 
170
170
 
171
171
  class _StubMetaBackend:
172
- def list_tables(self) -> 'list[TableDefinition]':
172
+ def list_tables(self, *, schema: 'str | None' = None) -> 'list[TableDefinition]':
173
173
  return [_table()]
174
174
 
175
175
  def describe_table(self, table_name: 'str') -> 'TableDefinition':
@@ -190,19 +190,15 @@ def _make_app(tmp_path: 'Path') -> 'MaxCApp':
190
190
  return app
191
191
 
192
192
 
193
- def test_meta_list_tables_returns_cache_guidance_when_cache_is_empty(tmp_path: 'Path') -> 'None':
193
+ def test_meta_list_tables_returns_live_results_when_cache_is_empty(tmp_path: 'Path') -> 'None':
194
194
  app = _make_app(tmp_path)
195
195
 
196
196
  envelope = app.meta_list_tables()
197
197
 
198
- assert envelope.status == "cache_miss"
199
- assert envelope.metadata["source"] == "none"
200
- assert envelope.metadata["cache_available"] is False
201
- assert envelope.to_dict()["data"] == {
202
- "tables": [],
203
- "pagination": {"total": 0, "has_more": False},
204
- }
205
- assert envelope.to_dict()["agent_hints"]["action_ids"] == ["cache.build"]
198
+ assert envelope.status == "success"
199
+ tables = envelope.to_dict()["data"]["tables"]
200
+ assert len(tables) == 1
201
+ assert tables[0]["table_name"] == "sales.orders"
206
202
 
207
203
 
208
204
  def test_cache_build_returns_clear_metadata_and_async_build_completes(tmp_path: 'Path') -> 'None':
@@ -1207,3 +1207,144 @@ def test_not_found_error_renders_markdown_without_json_flag(
1207
1207
  assert "**Error**" in err_text
1208
1208
  assert "`NOT_FOUND`" in err_text
1209
1209
  assert "**Suggestion**" in err_text
1210
+
1211
+
1212
+ # ============================================================
1213
+ # Schema Passthrough Tests
1214
+ # ============================================================
1215
+
1216
+
1217
+ class _SchemaAwareODPS(FakeODPS):
1218
+ """Mock ODPS client that returns different tables per schema."""
1219
+
1220
+ _SCHEMA_TABLES = {
1221
+ None: ["default_table_a", "default_table_b"],
1222
+ "california_schools": ["frpm", "satscores", "schools"],
1223
+ }
1224
+
1225
+ def list_tables(self, *, project=None, schema=None):
1226
+ names = self._SCHEMA_TABLES.get(schema, [])
1227
+ return [
1228
+ type("FakeTable", (), {"name": n})()
1229
+ for n in names
1230
+ ]
1231
+
1232
+ def get_table(self, name, *, project=None, schema=None):
1233
+ # minimal stub for describe
1234
+ return type("FakeTable", (), {
1235
+ "name": name,
1236
+ "comment": "",
1237
+ "table_schema": type("Schema", (), {"columns": [], "partitions": []})(),
1238
+ "owner": "test_owner",
1239
+ "creation_time": None,
1240
+ "last_data_modified_time": None,
1241
+ "is_virtual_view": False,
1242
+ "size": 0,
1243
+ "lifecycle": None,
1244
+ })()
1245
+
1246
+
1247
+ def test_meta_list_tables_passes_schema_to_backend(
1248
+ tmp_path: 'Path', monkeypatch
1249
+ ) -> None:
1250
+ """meta list-tables --schema should list tables from the specified schema."""
1251
+ clear_odps_env(monkeypatch)
1252
+ isolate_home(monkeypatch, tmp_path)
1253
+ import odps
1254
+ monkeypatch.setattr(odps, "ODPS", _SchemaAwareODPS)
1255
+
1256
+ config_path = _make_config_with_odps(tmp_path)
1257
+ code, payload, _ = run_json_command(
1258
+ tmp_path, config_path,
1259
+ ["meta", "list-tables", "--schema", "california_schools", "--json"],
1260
+ )
1261
+
1262
+ assert code == 0
1263
+ assert payload["status"] == "success"
1264
+ table_names = [t["table_name"] for t in payload["data"]["tables"]]
1265
+ assert sorted(table_names) == ["frpm", "satscores", "schools"]
1266
+
1267
+
1268
+ def test_meta_list_tables_without_schema_uses_default(
1269
+ tmp_path: 'Path', monkeypatch
1270
+ ) -> None:
1271
+ """meta list-tables without --schema should list tables from default schema."""
1272
+ clear_odps_env(monkeypatch)
1273
+ isolate_home(monkeypatch, tmp_path)
1274
+ import odps
1275
+ monkeypatch.setattr(odps, "ODPS", _SchemaAwareODPS)
1276
+
1277
+ config_path = _make_config_with_odps(tmp_path)
1278
+ code, payload, _ = run_json_command(
1279
+ tmp_path, config_path,
1280
+ ["meta", "list-tables", "--json"],
1281
+ )
1282
+
1283
+ assert code == 0
1284
+ assert payload["status"] == "success"
1285
+ table_names = [t["table_name"] for t in payload["data"]["tables"]]
1286
+ assert sorted(table_names) == ["default_table_a", "default_table_b"]
1287
+
1288
+
1289
+ def test_cache_build_passes_schema_to_backend(
1290
+ tmp_path: 'Path', monkeypatch
1291
+ ) -> None:
1292
+ """cache build --schema should list tables from the specified schema."""
1293
+ clear_odps_env(monkeypatch)
1294
+ isolate_home(monkeypatch, tmp_path)
1295
+ import odps
1296
+ monkeypatch.setattr(odps, "ODPS", _SchemaAwareODPS)
1297
+
1298
+ config_path = _make_config_with_odps(tmp_path)
1299
+ code, payload, _ = run_json_command(
1300
+ tmp_path, config_path,
1301
+ ["cache", "build", "--schema", "california_schools", "--json"],
1302
+ )
1303
+
1304
+ assert code == 0
1305
+ assert payload["data"]["tables_scanned"] == 3
1306
+ assert payload["data"]["cached_tables"] == 3
1307
+
1308
+
1309
+ def test_meta_search_passes_schema_to_backend(
1310
+ tmp_path: 'Path', monkeypatch
1311
+ ) -> None:
1312
+ """meta search --schema should search tables in the specified schema."""
1313
+ clear_odps_env(monkeypatch)
1314
+ isolate_home(monkeypatch, tmp_path)
1315
+ import odps
1316
+ monkeypatch.setattr(odps, "ODPS", _SchemaAwareODPS)
1317
+
1318
+ config_path = _make_config_with_odps(tmp_path)
1319
+ code, payload, _ = run_json_command(
1320
+ tmp_path, config_path,
1321
+ ["meta", "search", "frpm", "--schema", "california_schools", "--json"],
1322
+ )
1323
+
1324
+ assert code == 0
1325
+ assert payload["status"] == "success"
1326
+ matches = payload["data"]["search"]["matches"]
1327
+ assert len(matches) >= 1
1328
+ assert any(m["table_name"] == "frpm" for m in matches)
1329
+
1330
+
1331
+ def test_meta_search_columns_passes_schema_to_backend(
1332
+ tmp_path: 'Path', monkeypatch
1333
+ ) -> None:
1334
+ """meta search-columns --schema should search in the specified schema."""
1335
+ clear_odps_env(monkeypatch)
1336
+ isolate_home(monkeypatch, tmp_path)
1337
+ import odps
1338
+ monkeypatch.setattr(odps, "ODPS", _SchemaAwareODPS)
1339
+
1340
+ config_path = _make_config_with_odps(tmp_path)
1341
+ # search for a keyword that won't match (stub tables have no columns),
1342
+ # but verify it runs without error and uses the right schema
1343
+ code, payload, _ = run_json_command(
1344
+ tmp_path, config_path,
1345
+ ["meta", "search-columns", "nonexistent", "--schema", "california_schools", "--json"],
1346
+ )
1347
+
1348
+ assert code == 0
1349
+ assert payload["status"] == "success"
1350
+ assert payload["data"]["search"]["matches"] == []
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes