tinet-data-foundry-cli 0.1.5__tar.gz → 0.1.6__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 (19) hide show
  1. {tinet_data_foundry_cli-0.1.5 → tinet_data_foundry_cli-0.1.6}/PKG-INFO +13 -2
  2. {tinet_data_foundry_cli-0.1.5 → tinet_data_foundry_cli-0.1.6}/README.md +12 -1
  3. {tinet_data_foundry_cli-0.1.5 → tinet_data_foundry_cli-0.1.6}/pyproject.toml +1 -1
  4. tinet_data_foundry_cli-0.1.6/src/tinet_data_foundry_cli/__init__.py +1 -0
  5. {tinet_data_foundry_cli-0.1.5 → tinet_data_foundry_cli-0.1.6}/src/tinet_data_foundry_cli/cli.py +7 -4
  6. {tinet_data_foundry_cli-0.1.5 → tinet_data_foundry_cli-0.1.6}/src/tinet_data_foundry_cli/docs.py +88 -0
  7. {tinet_data_foundry_cli-0.1.5 → tinet_data_foundry_cli-0.1.6}/src/tinet_data_foundry_cli.egg-info/PKG-INFO +13 -2
  8. {tinet_data_foundry_cli-0.1.5 → tinet_data_foundry_cli-0.1.6}/tests/test_cli.py +3 -0
  9. {tinet_data_foundry_cli-0.1.5 → tinet_data_foundry_cli-0.1.6}/tests/test_docs.py +25 -1
  10. tinet_data_foundry_cli-0.1.5/src/tinet_data_foundry_cli/__init__.py +0 -1
  11. {tinet_data_foundry_cli-0.1.5 → tinet_data_foundry_cli-0.1.6}/setup.cfg +0 -0
  12. {tinet_data_foundry_cli-0.1.5 → tinet_data_foundry_cli-0.1.6}/src/tinet_data_foundry_cli/__main__.py +0 -0
  13. {tinet_data_foundry_cli-0.1.5 → tinet_data_foundry_cli-0.1.6}/src/tinet_data_foundry_cli/auth.py +0 -0
  14. {tinet_data_foundry_cli-0.1.5 → tinet_data_foundry_cli-0.1.6}/src/tinet_data_foundry_cli/client.py +0 -0
  15. {tinet_data_foundry_cli-0.1.5 → tinet_data_foundry_cli-0.1.6}/src/tinet_data_foundry_cli.egg-info/SOURCES.txt +0 -0
  16. {tinet_data_foundry_cli-0.1.5 → tinet_data_foundry_cli-0.1.6}/src/tinet_data_foundry_cli.egg-info/dependency_links.txt +0 -0
  17. {tinet_data_foundry_cli-0.1.5 → tinet_data_foundry_cli-0.1.6}/src/tinet_data_foundry_cli.egg-info/entry_points.txt +0 -0
  18. {tinet_data_foundry_cli-0.1.5 → tinet_data_foundry_cli-0.1.6}/src/tinet_data_foundry_cli.egg-info/top_level.txt +0 -0
  19. {tinet_data_foundry_cli-0.1.5 → tinet_data_foundry_cli-0.1.6}/tests/test_sign.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: tinet-data-foundry-cli
3
- Version: 0.1.5
3
+ Version: 0.1.6
4
4
  Summary: Tinet Data Foundry 取数 CLI(docs / catalog / query / schema)
5
5
  Author: Tinet
6
6
  License: MIT
@@ -55,7 +55,7 @@ tdf query \
55
55
  | `ends_with` | 左模糊,匹配后缀 | `{"op":"ends_with","field":"name","value":"公司"}` |
56
56
  | `contains` | 左右模糊 | `{"op":"contains","field":"name","value":"科技"}` |
57
57
 
58
- 分区表可以不传分区字段。服务端按上海时区补默认闭区间:按日为当天,按月为当月,按年为当年,并在响应 `data.partitionWindow` 里返回实际 `field/from/to`。显式 `between` 时,按日不超过 30 天,按月不超过 12 个月,按年不超过 3 年,否则 422 `PARTITION_RANGE_INVALID`。
58
+ 仅按日分区表必须带分区条件。未传分区字段时,服务端按上海时区补当天闭区间,并在响应 `data.partitionWindow` 里返回实际 `field/from/to`。显式 `between` 不超过 30 天,否则 422 `PARTITION_RANGE_INVALID`。按月、按年分区字段不是必填条件。
59
59
 
60
60
  若 pip 提示 scripts 目录不在 PATH,把该目录加入 PATH,或用 `python3 -m tinet_data_foundry_cli`。
61
61
 
@@ -92,6 +92,17 @@ tdf catalog --insecure
92
92
 
93
93
  文档站点默认 `https://data-foundry.tinetcloud.com`,可用 `DF_DOCS_BASE` 覆盖。`tdf docs` 只需要 `DF_AK`。缓存位于 `~/.cache/tinet-data-foundry/docs/<AK 哈希>/`,可用 `DF_DOCS_CACHE` 改根目录。stdout 非终端时 `tdf catalog` 改为 JSON。
94
94
 
95
+ ### Skill 查询大目录
96
+
97
+ `tdf docs pull` 把公共目录与授权策略缓存到本机。Skill / Agent 不应直接把大 `public.json` 读进上下文,而是通过以下命令只取需要的精简结果:
98
+
99
+ ```bash
100
+ tdf docs search --query "京东云消费"
101
+ tdf docs describe --dataset "DATASET_ID"
102
+ ```
103
+
104
+ `search` 默认只返回有表权限的数据集;`describe` 只返回该表已授权字段及其脱敏规则。首次使用先执行 `tdf docs pull`。
105
+
95
106
  Agent / Skill 传查询体用文件,避免 shell 转义:
96
107
 
97
108
  ```bash
@@ -38,7 +38,7 @@ tdf query \
38
38
  | `ends_with` | 左模糊,匹配后缀 | `{"op":"ends_with","field":"name","value":"公司"}` |
39
39
  | `contains` | 左右模糊 | `{"op":"contains","field":"name","value":"科技"}` |
40
40
 
41
- 分区表可以不传分区字段。服务端按上海时区补默认闭区间:按日为当天,按月为当月,按年为当年,并在响应 `data.partitionWindow` 里返回实际 `field/from/to`。显式 `between` 时,按日不超过 30 天,按月不超过 12 个月,按年不超过 3 年,否则 422 `PARTITION_RANGE_INVALID`。
41
+ 仅按日分区表必须带分区条件。未传分区字段时,服务端按上海时区补当天闭区间,并在响应 `data.partitionWindow` 里返回实际 `field/from/to`。显式 `between` 不超过 30 天,否则 422 `PARTITION_RANGE_INVALID`。按月、按年分区字段不是必填条件。
42
42
 
43
43
  若 pip 提示 scripts 目录不在 PATH,把该目录加入 PATH,或用 `python3 -m tinet_data_foundry_cli`。
44
44
 
@@ -75,6 +75,17 @@ tdf catalog --insecure
75
75
 
76
76
  文档站点默认 `https://data-foundry.tinetcloud.com`,可用 `DF_DOCS_BASE` 覆盖。`tdf docs` 只需要 `DF_AK`。缓存位于 `~/.cache/tinet-data-foundry/docs/<AK 哈希>/`,可用 `DF_DOCS_CACHE` 改根目录。stdout 非终端时 `tdf catalog` 改为 JSON。
77
77
 
78
+ ### Skill 查询大目录
79
+
80
+ `tdf docs pull` 把公共目录与授权策略缓存到本机。Skill / Agent 不应直接把大 `public.json` 读进上下文,而是通过以下命令只取需要的精简结果:
81
+
82
+ ```bash
83
+ tdf docs search --query "京东云消费"
84
+ tdf docs describe --dataset "DATASET_ID"
85
+ ```
86
+
87
+ `search` 默认只返回有表权限的数据集;`describe` 只返回该表已授权字段及其脱敏规则。首次使用先执行 `tdf docs pull`。
88
+
78
89
  Agent / Skill 传查询体用文件,避免 shell 转义:
79
90
 
80
91
  ```bash
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "tinet-data-foundry-cli"
7
- version = "0.1.5"
7
+ version = "0.1.6"
8
8
  description = "Tinet Data Foundry 取数 CLI(docs / catalog / query / schema)"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.10"
@@ -0,0 +1 @@
1
+ __version__ = "0.1.6"
@@ -259,8 +259,8 @@ def build_parser() -> argparse.ArgumentParser:
259
259
  "--filter",
260
260
  help=(
261
261
  "filter AST JSON。字符串字段可 starts_with(右模糊)、ends_with(左模糊)、contains(左右模糊)。"
262
- "分区表省略分区字段时,按日补当天、按月补当月、按年补当年;"
263
- "显式 between 按日不超过 30 天、按月不超过 12 个月、按年不超过 3 年"
262
+ "仅按日分区必填:省略分区字段时按上海时区补当天,显式 between 不超过 30 天。"
263
+ "按月、按年分区字段不是必填条件"
264
264
  ),
265
265
  )
266
266
  q.add_argument("--sort", help="sort JSON 数组")
@@ -277,9 +277,12 @@ def build_parser() -> argparse.ArgumentParser:
277
277
  )
278
278
  q.set_defaults(func=cmd_query)
279
279
 
280
- d = sub.add_parser("docs", help="核对数据集开发文档修订号并缓存 public/policy")
280
+ d = sub.add_parser("docs", help="核对数据集开发文档修订号,并在本地缓存中检索数据集")
281
281
  _add_insecure(d, overlay=True)
282
- d.add_argument("action", choices=["rev", "pull"])
282
+ d.add_argument("action", choices=["rev", "pull", "search", "describe"])
283
+ d.add_argument("--query", help="search:按名称、描述、表名或业务域检索本地缓存")
284
+ d.add_argument("--dataset", help="describe:输出指定数据集的已授权字段")
285
+ d.add_argument("--all", action="store_true", help="search:包含无表权限的数据集")
283
286
  d.add_argument("--docs-base", help="覆盖文档站点。默认 DF_DOCS_BASE 或 https://data-foundry.tinetcloud.com")
284
287
  d.set_defaults(func=cmd_docs)
285
288
 
@@ -47,6 +47,10 @@ def run_docs(args: Any) -> int:
47
47
  ak = access_key()
48
48
  base = docs_base(getattr(args, "docs_base", None))
49
49
  folder = cache_dir(ak)
50
+ if args.action == "search":
51
+ return _search(folder, str(getattr(args, "query", "") or ""), bool(getattr(args, "all", False)))
52
+ if args.action == "describe":
53
+ return _describe(folder, str(getattr(args, "dataset", "") or ""))
50
54
  url = docs_url(base, ak, "rev")
51
55
  if args.dry_run:
52
56
  _print({
@@ -64,6 +68,90 @@ def run_docs(args: Any) -> int:
64
68
  return _pull(folder, base, ak, args.timeout, args.insecure)
65
69
 
66
70
 
71
+ def _cached_docs(folder: Path) -> tuple[Dict[str, Any], Dict[str, Any]]:
72
+ public = _read_json(folder / "public.json")
73
+ policy = _read_json(folder / "policy.json")
74
+ if not public or not policy:
75
+ raise SystemExit("本地文档缓存不存在,请先运行 tdf docs pull")
76
+ return public, policy
77
+
78
+
79
+ def _search(folder: Path, query: str, include_denied: bool) -> int:
80
+ public, policy = _cached_docs(folder)
81
+ needle = query.strip().casefold()
82
+ if not needle:
83
+ raise SystemExit("docs search 需要 --query")
84
+ grants = _index(policy)
85
+ matches: List[Dict[str, Any]] = []
86
+ for dataset in public.get("datasets") or []:
87
+ if not isinstance(dataset, dict):
88
+ continue
89
+ grant = grants.get(str(dataset.get("id") or ""), {})
90
+ if not include_denied and not grant.get("tableGranted"):
91
+ continue
92
+ haystack = " ".join(str(dataset.get(key) or "") for key in (
93
+ "id", "name", "chineseName", "tableComment", "description", "businessDomainName",
94
+ )).casefold()
95
+ if needle not in haystack:
96
+ continue
97
+ matches.append({
98
+ "id": dataset.get("id"),
99
+ "name": dataset.get("name"),
100
+ "chineseName": dataset.get("chineseName"),
101
+ "description": dataset.get("description"),
102
+ "businessDomainName": dataset.get("businessDomainName"),
103
+ "layer": dataset.get("layer"),
104
+ "schemaVersion": dataset.get("schemaVersion"),
105
+ "tableGranted": bool(grant.get("tableGranted")),
106
+ })
107
+ _print({"query": query, "datasets": matches})
108
+ return 0
109
+
110
+
111
+ def _describe(folder: Path, dataset_id: str) -> int:
112
+ public, policy = _cached_docs(folder)
113
+ if not dataset_id.strip():
114
+ raise SystemExit("docs describe 需要 --dataset")
115
+ datasets = _index(public)
116
+ dataset = datasets.get(dataset_id)
117
+ if not dataset:
118
+ raise SystemExit(f"缓存中没有数据集 {dataset_id};先执行 tdf docs search --query <关键词>")
119
+ grant = _index(policy).get(dataset_id, {})
120
+ rules = {
121
+ str(col.get("name")): col
122
+ for col in grant.get("columns") or []
123
+ if isinstance(col, dict) and col.get("granted") and col.get("name")
124
+ }
125
+ columns = []
126
+ for column in dataset.get("columns") or []:
127
+ if not isinstance(column, dict) or str(column.get("name")) not in rules:
128
+ continue
129
+ name = str(column["name"])
130
+ columns.append({
131
+ "name": name,
132
+ "type": column.get("type"),
133
+ "comment": column.get("comment"),
134
+ "pk": column.get("pk"),
135
+ "partition": column.get("partition"),
136
+ "partitionGrain": column.get("partitionGrain"),
137
+ "partitionRequired": column.get("partitionRequired"),
138
+ "outputRule": rules[name].get("outputRule"),
139
+ })
140
+ _print({
141
+ "id": dataset.get("id"),
142
+ "name": dataset.get("name"),
143
+ "chineseName": dataset.get("chineseName"),
144
+ "description": dataset.get("description"),
145
+ "businessDomainName": dataset.get("businessDomainName"),
146
+ "schemaVersion": dataset.get("schemaVersion"),
147
+ "primaryKey": dataset.get("primaryKey"),
148
+ "partitionFields": dataset.get("partitionFields"),
149
+ "tableGranted": bool(grant.get("tableGranted")),
150
+ "columns": columns,
151
+ })
152
+ return 0
153
+
154
+
67
155
  def _pull(folder: Path, base: str, ak: str, timeout: float, insecure: bool) -> int:
68
156
  local = _read_json(folder / "rev.json")
69
157
  local_etag = str(local.get("etag") or "")
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: tinet-data-foundry-cli
3
- Version: 0.1.5
3
+ Version: 0.1.6
4
4
  Summary: Tinet Data Foundry 取数 CLI(docs / catalog / query / schema)
5
5
  Author: Tinet
6
6
  License: MIT
@@ -55,7 +55,7 @@ tdf query \
55
55
  | `ends_with` | 左模糊,匹配后缀 | `{"op":"ends_with","field":"name","value":"公司"}` |
56
56
  | `contains` | 左右模糊 | `{"op":"contains","field":"name","value":"科技"}` |
57
57
 
58
- 分区表可以不传分区字段。服务端按上海时区补默认闭区间:按日为当天,按月为当月,按年为当年,并在响应 `data.partitionWindow` 里返回实际 `field/from/to`。显式 `between` 时,按日不超过 30 天,按月不超过 12 个月,按年不超过 3 年,否则 422 `PARTITION_RANGE_INVALID`。
58
+ 仅按日分区表必须带分区条件。未传分区字段时,服务端按上海时区补当天闭区间,并在响应 `data.partitionWindow` 里返回实际 `field/from/to`。显式 `between` 不超过 30 天,否则 422 `PARTITION_RANGE_INVALID`。按月、按年分区字段不是必填条件。
59
59
 
60
60
  若 pip 提示 scripts 目录不在 PATH,把该目录加入 PATH,或用 `python3 -m tinet_data_foundry_cli`。
61
61
 
@@ -92,6 +92,17 @@ tdf catalog --insecure
92
92
 
93
93
  文档站点默认 `https://data-foundry.tinetcloud.com`,可用 `DF_DOCS_BASE` 覆盖。`tdf docs` 只需要 `DF_AK`。缓存位于 `~/.cache/tinet-data-foundry/docs/<AK 哈希>/`,可用 `DF_DOCS_CACHE` 改根目录。stdout 非终端时 `tdf catalog` 改为 JSON。
94
94
 
95
+ ### Skill 查询大目录
96
+
97
+ `tdf docs pull` 把公共目录与授权策略缓存到本机。Skill / Agent 不应直接把大 `public.json` 读进上下文,而是通过以下命令只取需要的精简结果:
98
+
99
+ ```bash
100
+ tdf docs search --query "京东云消费"
101
+ tdf docs describe --dataset "DATASET_ID"
102
+ ```
103
+
104
+ `search` 默认只返回有表权限的数据集;`describe` 只返回该表已授权字段及其脱敏规则。首次使用先执行 `tdf docs pull`。
105
+
95
106
  Agent / Skill 传查询体用文件,避免 shell 转义:
96
107
 
97
108
  ```bash
@@ -17,8 +17,11 @@ class CliParserTest(unittest.TestCase):
17
17
  self.assertIn("starts_with", help_text)
18
18
  self.assertIn("ends_with", help_text)
19
19
  self.assertIn("contains", help_text)
20
+ self.assertIn("仅按日分区必填", help_text)
20
21
  self.assertIn("30", help_text)
21
22
  self.assertIn("当天", help_text)
23
+ self.assertIn("按月、按年分区字段不是必填条件", help_text)
24
+ self.assertNotIn("按月补当月", help_text)
22
25
 
23
26
  def test_subcommands(self):
24
27
  parser = build_parser()
@@ -1,13 +1,37 @@
1
1
  import json
2
2
  import tempfile
3
3
  import unittest
4
+ from io import StringIO
4
5
  from pathlib import Path
5
6
  from unittest.mock import patch
6
7
 
7
- from tinet_data_foundry_cli.docs import summarize
8
+ from tinet_data_foundry_cli.docs import _describe, _search, summarize
8
9
 
9
10
 
10
11
  class DocsSummaryTest(unittest.TestCase):
12
+ def test_search_and_describe_only_print_authorized_dataset_content(self):
13
+ with tempfile.TemporaryDirectory() as tmp, patch("sys.stdout", new_callable=StringIO) as out:
14
+ folder = Path(tmp)
15
+ (folder / "public.json").write_text(json.dumps({"datasets": [{
16
+ "id": "ds_bill", "name": "monthly_bill", "chineseName": "月账单",
17
+ "description": "京东云消费", "columns": [
18
+ {"name": "amount", "type": "decimal", "comment": "金额"},
19
+ {"name": "secret", "type": "text", "comment": "无权限"},
20
+ ],
21
+ }]}), encoding="utf-8")
22
+ (folder / "policy.json").write_text(json.dumps({"datasets": [{
23
+ "id": "ds_bill", "tableGranted": True,
24
+ "columns": [{"name": "amount", "granted": True, "outputRule": "PLAIN"}],
25
+ }]}), encoding="utf-8")
26
+ self.assertEqual(_search(folder, "京东云", False), 0)
27
+ search = json.loads(out.getvalue())
28
+ self.assertEqual(search["datasets"][0]["id"], "ds_bill")
29
+ out.seek(0)
30
+ out.truncate(0)
31
+ self.assertEqual(_describe(folder, "ds_bill"), 0)
32
+ described = json.loads(out.getvalue())
33
+ self.assertEqual([col["name"] for col in described["columns"]], ["amount"])
34
+
11
35
  def test_summarize_catalog_and_policy(self):
12
36
  old_public = {"datasets": [{"id": "a", "schemaVersion": "1"}, {"id": "b", "schemaVersion": "1"}]}
13
37
  new_public = {"datasets": [{"id": "b", "schemaVersion": "2"}, {"id": "c", "schemaVersion": "1"}]}
@@ -1 +0,0 @@
1
- __version__ = "0.1.5"