clearmetric-core 0.2.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.
- clearmetric_core-0.2.0/PKG-INFO +91 -0
- clearmetric_core-0.2.0/README.md +60 -0
- clearmetric_core-0.2.0/pyproject.toml +69 -0
- clearmetric_core-0.2.0/setup.cfg +4 -0
- clearmetric_core-0.2.0/src/clearmetric/cli/__init__.py +157 -0
- clearmetric_core-0.2.0/src/clearmetric/cli/__main__.py +8 -0
- clearmetric_core-0.2.0/src/clearmetric/core/__init__.py +78 -0
- clearmetric_core-0.2.0/src/clearmetric/core/_version.py +3 -0
- clearmetric_core-0.2.0/src/clearmetric/core/aliases.py +91 -0
- clearmetric_core-0.2.0/src/clearmetric/core/errors.py +19 -0
- clearmetric_core-0.2.0/src/clearmetric/core/ids.py +172 -0
- clearmetric_core-0.2.0/src/clearmetric/core/interop.py +126 -0
- clearmetric_core-0.2.0/src/clearmetric/core/merge.py +147 -0
- clearmetric_core-0.2.0/src/clearmetric/core/models.py +68 -0
- clearmetric_core-0.2.0/src/clearmetric/core/serialize.py +10 -0
- clearmetric_core-0.2.0/src/clearmetric/lineage/__init__.py +36 -0
- clearmetric_core-0.2.0/src/clearmetric/lineage/_version.py +5 -0
- clearmetric_core-0.2.0/src/clearmetric/lineage/api.py +86 -0
- clearmetric_core-0.2.0/src/clearmetric/lineage/build.py +961 -0
- clearmetric_core-0.2.0/src/clearmetric/lineage/coverage.py +198 -0
- clearmetric_core-0.2.0/src/clearmetric/lineage/errors.py +15 -0
- clearmetric_core-0.2.0/src/clearmetric/lineage/graph.py +143 -0
- clearmetric_core-0.2.0/src/clearmetric/lineage/loaders.py +249 -0
- clearmetric_core-0.2.0/src/clearmetric/lineage/models.py +34 -0
- clearmetric_core-0.2.0/src/clearmetric/lineage/render/json.py +10 -0
- clearmetric_core-0.2.0/src/clearmetric/lineage/render/mermaid.py +40 -0
- clearmetric_core-0.2.0/src/clearmetric/lineage/render/text.py +108 -0
- clearmetric_core-0.2.0/src/clearmetric/lineage/sql_analyzer.py +409 -0
- clearmetric_core-0.2.0/src/clearmetric/powerbi/__init__.py +28 -0
- clearmetric_core-0.2.0/src/clearmetric/powerbi/_version.py +5 -0
- clearmetric_core-0.2.0/src/clearmetric/powerbi/api.py +55 -0
- clearmetric_core-0.2.0/src/clearmetric/powerbi/build.py +369 -0
- clearmetric_core-0.2.0/src/clearmetric/powerbi/discovery.py +109 -0
- clearmetric_core-0.2.0/src/clearmetric/powerbi/errors.py +20 -0
- clearmetric_core-0.2.0/src/clearmetric/powerbi/m_parser.py +242 -0
- clearmetric_core-0.2.0/src/clearmetric/powerbi/models.py +63 -0
- clearmetric_core-0.2.0/src/clearmetric/powerbi/native_sql.py +35 -0
- clearmetric_core-0.2.0/src/clearmetric/powerbi/render/json.py +15 -0
- clearmetric_core-0.2.0/src/clearmetric/powerbi/render/text.py +31 -0
- clearmetric_core-0.2.0/src/clearmetric/powerbi/report_parser.py +245 -0
- clearmetric_core-0.2.0/src/clearmetric/powerbi/tmdl.py +59 -0
- clearmetric_core-0.2.0/src/clearmetric/query/__init__.py +39 -0
- clearmetric_core-0.2.0/src/clearmetric/query/_version.py +5 -0
- clearmetric_core-0.2.0/src/clearmetric/query/api.py +39 -0
- clearmetric_core-0.2.0/src/clearmetric/query/ast_utils.py +81 -0
- clearmetric_core-0.2.0/src/clearmetric/query/build.py +263 -0
- clearmetric_core-0.2.0/src/clearmetric/query/ctes.py +127 -0
- clearmetric_core-0.2.0/src/clearmetric/query/errors.py +15 -0
- clearmetric_core-0.2.0/src/clearmetric/query/models.py +93 -0
- clearmetric_core-0.2.0/src/clearmetric/query/parser.py +67 -0
- clearmetric_core-0.2.0/src/clearmetric/query/relations.py +229 -0
- clearmetric_core-0.2.0/src/clearmetric/query/render/__init__.py +1 -0
- clearmetric_core-0.2.0/src/clearmetric/query/render/json.py +10 -0
- clearmetric_core-0.2.0/src/clearmetric/query/render/text.py +42 -0
- clearmetric_core-0.2.0/src/clearmetric_core.egg-info/PKG-INFO +91 -0
- clearmetric_core-0.2.0/src/clearmetric_core.egg-info/SOURCES.txt +63 -0
- clearmetric_core-0.2.0/src/clearmetric_core.egg-info/dependency_links.txt +1 -0
- clearmetric_core-0.2.0/src/clearmetric_core.egg-info/entry_points.txt +2 -0
- clearmetric_core-0.2.0/src/clearmetric_core.egg-info/requires.txt +11 -0
- clearmetric_core-0.2.0/src/clearmetric_core.egg-info/top_level.txt +1 -0
- clearmetric_core-0.2.0/tests/test_aliases.py +63 -0
- clearmetric_core-0.2.0/tests/test_contract.py +36 -0
- clearmetric_core-0.2.0/tests/test_ids.py +29 -0
- clearmetric_core-0.2.0/tests/test_interop.py +89 -0
- clearmetric_core-0.2.0/tests/test_merge.py +166 -0
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: clearmetric-core
|
|
3
|
+
Version: 0.2.0
|
|
4
|
+
Summary: ClearMetric Core — local compiler, graph engine, and CLI foundation.
|
|
5
|
+
Author: ClearMetric Labs
|
|
6
|
+
License-Expression: Apache-2.0
|
|
7
|
+
Project-URL: Homepage, https://github.com/ClearMetric-Labs/ClearMetric-Core
|
|
8
|
+
Project-URL: Source, https://github.com/ClearMetric-Labs/ClearMetric-Core
|
|
9
|
+
Project-URL: Issues, https://github.com/ClearMetric-Labs/ClearMetric-Core/issues
|
|
10
|
+
Keywords: catalog,lineage,metadata,graph,sql,dbt,powerbi,analytics
|
|
11
|
+
Classifier: Development Status :: 3 - Alpha
|
|
12
|
+
Classifier: Intended Audience :: Developers
|
|
13
|
+
Classifier: Programming Language :: Python :: 3
|
|
14
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
18
|
+
Classifier: Topic :: Database
|
|
19
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
20
|
+
Requires-Python: >=3.11
|
|
21
|
+
Description-Content-Type: text/markdown
|
|
22
|
+
Requires-Dist: pydantic>=2.10.0
|
|
23
|
+
Requires-Dist: sqlglot>=25.0.0
|
|
24
|
+
Requires-Dist: pbi-parsers>=0.9.5
|
|
25
|
+
Provides-Extra: dev
|
|
26
|
+
Requires-Dist: pytest>=7.0.0; extra == "dev"
|
|
27
|
+
Requires-Dist: PyYAML>=6.0; extra == "dev"
|
|
28
|
+
Provides-Extra: release
|
|
29
|
+
Requires-Dist: build>=1.2.2; extra == "release"
|
|
30
|
+
Requires-Dist: twine>=5.1.1; extra == "release"
|
|
31
|
+
|
|
32
|
+
# clearmetric-core
|
|
33
|
+
|
|
34
|
+
One PyPI package. All ClearMetric Core modules (`clearmetric.core`, `.lineage`,
|
|
35
|
+
`.query`, `.powerbi`, `.cli`) ship inside this distribution.
|
|
36
|
+
|
|
37
|
+
## Install
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
python -m pip install clearmetric-core
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
## CLI
|
|
44
|
+
|
|
45
|
+
```bash
|
|
46
|
+
cm compile ./manifest.json --dialect postgres
|
|
47
|
+
cm impact orders.amount ./manifest.json --dialect postgres --upstream
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
If `cm` is occupied on your PATH:
|
|
51
|
+
|
|
52
|
+
```bash
|
|
53
|
+
python -m clearmetric.cli compile ./manifest.json --dialect postgres
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
## Modules
|
|
57
|
+
|
|
58
|
+
| Module | Purpose |
|
|
59
|
+
|--------|---------|
|
|
60
|
+
| `clearmetric.core` | Artifact schema, canonical IDs, merge, cross-graph interop |
|
|
61
|
+
| `clearmetric.lineage` | Project-level SQL lineage from dbt manifests and SQL folders |
|
|
62
|
+
| `clearmetric.query` | Single-statement SQL structure mapping |
|
|
63
|
+
| `clearmetric.powerbi` | PBIP file lineage and warehouse merge |
|
|
64
|
+
| `clearmetric.cli` | `cm` command router |
|
|
65
|
+
|
|
66
|
+
## Imports
|
|
67
|
+
|
|
68
|
+
```python
|
|
69
|
+
from clearmetric.core import (
|
|
70
|
+
CatalogArtifact,
|
|
71
|
+
Edge,
|
|
72
|
+
Evidence,
|
|
73
|
+
Node,
|
|
74
|
+
Warning,
|
|
75
|
+
load_table_alias_map,
|
|
76
|
+
merge,
|
|
77
|
+
resolve_table_match,
|
|
78
|
+
)
|
|
79
|
+
from clearmetric.lineage import build_catalog_artifact, trace_upstream
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
For local development:
|
|
83
|
+
|
|
84
|
+
```bash
|
|
85
|
+
python -m pip install -e ".[dev,release]"
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
## Contract
|
|
89
|
+
|
|
90
|
+
The source of truth for the shared artifact contract is
|
|
91
|
+
[`docs/contract.md`](docs/contract.md).
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
# clearmetric-core
|
|
2
|
+
|
|
3
|
+
One PyPI package. All ClearMetric Core modules (`clearmetric.core`, `.lineage`,
|
|
4
|
+
`.query`, `.powerbi`, `.cli`) ship inside this distribution.
|
|
5
|
+
|
|
6
|
+
## Install
|
|
7
|
+
|
|
8
|
+
```bash
|
|
9
|
+
python -m pip install clearmetric-core
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
## CLI
|
|
13
|
+
|
|
14
|
+
```bash
|
|
15
|
+
cm compile ./manifest.json --dialect postgres
|
|
16
|
+
cm impact orders.amount ./manifest.json --dialect postgres --upstream
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
If `cm` is occupied on your PATH:
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
python -m clearmetric.cli compile ./manifest.json --dialect postgres
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
## Modules
|
|
26
|
+
|
|
27
|
+
| Module | Purpose |
|
|
28
|
+
|--------|---------|
|
|
29
|
+
| `clearmetric.core` | Artifact schema, canonical IDs, merge, cross-graph interop |
|
|
30
|
+
| `clearmetric.lineage` | Project-level SQL lineage from dbt manifests and SQL folders |
|
|
31
|
+
| `clearmetric.query` | Single-statement SQL structure mapping |
|
|
32
|
+
| `clearmetric.powerbi` | PBIP file lineage and warehouse merge |
|
|
33
|
+
| `clearmetric.cli` | `cm` command router |
|
|
34
|
+
|
|
35
|
+
## Imports
|
|
36
|
+
|
|
37
|
+
```python
|
|
38
|
+
from clearmetric.core import (
|
|
39
|
+
CatalogArtifact,
|
|
40
|
+
Edge,
|
|
41
|
+
Evidence,
|
|
42
|
+
Node,
|
|
43
|
+
Warning,
|
|
44
|
+
load_table_alias_map,
|
|
45
|
+
merge,
|
|
46
|
+
resolve_table_match,
|
|
47
|
+
)
|
|
48
|
+
from clearmetric.lineage import build_catalog_artifact, trace_upstream
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
For local development:
|
|
52
|
+
|
|
53
|
+
```bash
|
|
54
|
+
python -m pip install -e ".[dev,release]"
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
## Contract
|
|
58
|
+
|
|
59
|
+
The source of truth for the shared artifact contract is
|
|
60
|
+
[`docs/contract.md`](docs/contract.md).
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=61.0", "wheel"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "clearmetric-core"
|
|
7
|
+
dynamic = ["version"]
|
|
8
|
+
description = "ClearMetric Core — local compiler, graph engine, and CLI foundation."
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.11"
|
|
11
|
+
license = "Apache-2.0"
|
|
12
|
+
authors = [
|
|
13
|
+
{name = "ClearMetric Labs"},
|
|
14
|
+
]
|
|
15
|
+
keywords = [
|
|
16
|
+
"catalog",
|
|
17
|
+
"lineage",
|
|
18
|
+
"metadata",
|
|
19
|
+
"graph",
|
|
20
|
+
"sql",
|
|
21
|
+
"dbt",
|
|
22
|
+
"powerbi",
|
|
23
|
+
"analytics",
|
|
24
|
+
]
|
|
25
|
+
classifiers = [
|
|
26
|
+
"Development Status :: 3 - Alpha",
|
|
27
|
+
"Intended Audience :: Developers",
|
|
28
|
+
"Programming Language :: Python :: 3",
|
|
29
|
+
"Programming Language :: Python :: 3 :: Only",
|
|
30
|
+
"Programming Language :: Python :: 3.11",
|
|
31
|
+
"Programming Language :: Python :: 3.12",
|
|
32
|
+
"Programming Language :: Python :: 3.13",
|
|
33
|
+
"Topic :: Database",
|
|
34
|
+
"Topic :: Software Development :: Libraries :: Python Modules",
|
|
35
|
+
]
|
|
36
|
+
dependencies = [
|
|
37
|
+
"pydantic>=2.10.0",
|
|
38
|
+
"sqlglot>=25.0.0",
|
|
39
|
+
"pbi-parsers>=0.9.5",
|
|
40
|
+
]
|
|
41
|
+
|
|
42
|
+
[project.optional-dependencies]
|
|
43
|
+
dev = [
|
|
44
|
+
"pytest>=7.0.0",
|
|
45
|
+
"PyYAML>=6.0",
|
|
46
|
+
]
|
|
47
|
+
release = [
|
|
48
|
+
"build>=1.2.2",
|
|
49
|
+
"twine>=5.1.1",
|
|
50
|
+
]
|
|
51
|
+
|
|
52
|
+
[project.scripts]
|
|
53
|
+
cm = "clearmetric.cli:main"
|
|
54
|
+
|
|
55
|
+
[project.urls]
|
|
56
|
+
Homepage = "https://github.com/ClearMetric-Labs/ClearMetric-Core"
|
|
57
|
+
Source = "https://github.com/ClearMetric-Labs/ClearMetric-Core"
|
|
58
|
+
Issues = "https://github.com/ClearMetric-Labs/ClearMetric-Core/issues"
|
|
59
|
+
|
|
60
|
+
[tool.setuptools.dynamic]
|
|
61
|
+
version = {attr = "clearmetric.core._version.__version__"}
|
|
62
|
+
|
|
63
|
+
[tool.setuptools.package-dir]
|
|
64
|
+
"" = "src"
|
|
65
|
+
|
|
66
|
+
[tool.setuptools.packages.find]
|
|
67
|
+
where = ["src"]
|
|
68
|
+
include = ["clearmetric.*"]
|
|
69
|
+
namespaces = true
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
"""ClearMetric Core CLI — ``cm`` command router."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import argparse
|
|
6
|
+
import json
|
|
7
|
+
import sys
|
|
8
|
+
|
|
9
|
+
from clearmetric.core import __version__, render_json
|
|
10
|
+
from clearmetric.lineage import (
|
|
11
|
+
build_catalog_artifact,
|
|
12
|
+
build_lineage_map,
|
|
13
|
+
trace_downstream,
|
|
14
|
+
trace_upstream,
|
|
15
|
+
)
|
|
16
|
+
from clearmetric.lineage.errors import LineageError
|
|
17
|
+
from clearmetric.lineage.render.mermaid import render_traversal_mermaid
|
|
18
|
+
from clearmetric.lineage.render.text import render_text, render_traversal_tree
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def _build_root_parser() -> argparse.ArgumentParser:
|
|
22
|
+
parser = argparse.ArgumentParser(
|
|
23
|
+
prog="cm",
|
|
24
|
+
description="ClearMetric Core — local compiler, graph engine, and CLI.",
|
|
25
|
+
)
|
|
26
|
+
parser.add_argument(
|
|
27
|
+
"--version",
|
|
28
|
+
action="version",
|
|
29
|
+
version=f"cm {__version__} (ClearMetric Core)",
|
|
30
|
+
)
|
|
31
|
+
subparsers = parser.add_subparsers(dest="command", required=True)
|
|
32
|
+
|
|
33
|
+
compile_parser = subparsers.add_parser(
|
|
34
|
+
"compile",
|
|
35
|
+
help="Compile project input into a catalog graph artifact (JSON).",
|
|
36
|
+
)
|
|
37
|
+
compile_parser.add_argument(
|
|
38
|
+
"project_input",
|
|
39
|
+
help="Path to a dbt manifest.json file or a folder of UTF-8 .sql files.",
|
|
40
|
+
)
|
|
41
|
+
compile_parser.add_argument(
|
|
42
|
+
"--dialect",
|
|
43
|
+
required=True,
|
|
44
|
+
help="sqlglot dialect name, for example postgres, snowflake, tsql, or bigquery.",
|
|
45
|
+
)
|
|
46
|
+
compile_parser.add_argument(
|
|
47
|
+
"--format",
|
|
48
|
+
choices=("json", "text"),
|
|
49
|
+
default="json",
|
|
50
|
+
help="Output format (default: json).",
|
|
51
|
+
)
|
|
52
|
+
|
|
53
|
+
impact_parser = subparsers.add_parser(
|
|
54
|
+
"impact",
|
|
55
|
+
help="Trace upstream or downstream column lineage for one selection.",
|
|
56
|
+
)
|
|
57
|
+
impact_parser.add_argument(
|
|
58
|
+
"selection",
|
|
59
|
+
help="Dataset column selection, for example orders.amount.",
|
|
60
|
+
)
|
|
61
|
+
impact_parser.add_argument(
|
|
62
|
+
"project_input",
|
|
63
|
+
help="Path to a dbt manifest.json file or a folder of UTF-8 .sql files.",
|
|
64
|
+
)
|
|
65
|
+
impact_parser.add_argument(
|
|
66
|
+
"--dialect",
|
|
67
|
+
required=True,
|
|
68
|
+
help="sqlglot dialect name, for example postgres, snowflake, tsql, or bigquery.",
|
|
69
|
+
)
|
|
70
|
+
traversal = impact_parser.add_mutually_exclusive_group(required=True)
|
|
71
|
+
traversal.add_argument(
|
|
72
|
+
"--upstream",
|
|
73
|
+
action="store_true",
|
|
74
|
+
help="Trace upstream lineage for the selection.",
|
|
75
|
+
)
|
|
76
|
+
traversal.add_argument(
|
|
77
|
+
"--downstream",
|
|
78
|
+
action="store_true",
|
|
79
|
+
help="Trace downstream impact for the selection.",
|
|
80
|
+
)
|
|
81
|
+
impact_parser.add_argument(
|
|
82
|
+
"--format",
|
|
83
|
+
choices=("text", "json", "mermaid"),
|
|
84
|
+
default="text",
|
|
85
|
+
help="Output format (default: text).",
|
|
86
|
+
)
|
|
87
|
+
return parser
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
def _run_compile(args: argparse.Namespace) -> int:
|
|
91
|
+
if args.format == "json":
|
|
92
|
+
artifact = build_catalog_artifact(args.project_input, dialect=args.dialect)
|
|
93
|
+
print(json.dumps(render_json(artifact), indent=2, sort_keys=False))
|
|
94
|
+
else:
|
|
95
|
+
lineage_map = build_lineage_map(args.project_input, dialect=args.dialect)
|
|
96
|
+
print(render_text(lineage_map))
|
|
97
|
+
return 0
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
def _run_impact(args: argparse.Namespace) -> int:
|
|
101
|
+
direction = "upstream" if args.upstream else "downstream"
|
|
102
|
+
if direction == "upstream":
|
|
103
|
+
result = trace_upstream(
|
|
104
|
+
args.project_input,
|
|
105
|
+
dialect=args.dialect,
|
|
106
|
+
selection=args.selection,
|
|
107
|
+
)
|
|
108
|
+
else:
|
|
109
|
+
result = trace_downstream(
|
|
110
|
+
args.project_input,
|
|
111
|
+
dialect=args.dialect,
|
|
112
|
+
selection=args.selection,
|
|
113
|
+
)
|
|
114
|
+
|
|
115
|
+
if args.format == "json":
|
|
116
|
+
print(json.dumps(result.model_dump(mode="json"), indent=2, sort_keys=False))
|
|
117
|
+
return 0
|
|
118
|
+
|
|
119
|
+
artifact = build_catalog_artifact(args.project_input, dialect=args.dialect)
|
|
120
|
+
if args.format == "mermaid":
|
|
121
|
+
print(
|
|
122
|
+
render_traversal_mermaid(
|
|
123
|
+
result.selection_id,
|
|
124
|
+
artifact,
|
|
125
|
+
direction=direction,
|
|
126
|
+
)
|
|
127
|
+
)
|
|
128
|
+
return 0
|
|
129
|
+
|
|
130
|
+
print(
|
|
131
|
+
render_traversal_tree(
|
|
132
|
+
result,
|
|
133
|
+
artifact,
|
|
134
|
+
direction=direction,
|
|
135
|
+
)
|
|
136
|
+
)
|
|
137
|
+
return 0
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
def main(argv: list[str] | None = None) -> int:
|
|
141
|
+
parser = _build_root_parser()
|
|
142
|
+
args = parser.parse_args(argv)
|
|
143
|
+
|
|
144
|
+
try:
|
|
145
|
+
if args.command == "compile":
|
|
146
|
+
return _run_compile(args)
|
|
147
|
+
if args.command == "impact":
|
|
148
|
+
return _run_impact(args)
|
|
149
|
+
except LineageError as exc:
|
|
150
|
+
print(f"cm error: {exc}", file=sys.stderr)
|
|
151
|
+
return 1
|
|
152
|
+
|
|
153
|
+
print(f"cm: unknown command {args.command!r}", file=sys.stderr)
|
|
154
|
+
return 1
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
__all__ = ["main"]
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
"""Public package surface for clearmetric-core."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from ._version import __version__
|
|
6
|
+
from .aliases import load_table_alias_map
|
|
7
|
+
from .errors import (
|
|
8
|
+
AliasMapError,
|
|
9
|
+
CanonicalIdError,
|
|
10
|
+
ClearMetricError,
|
|
11
|
+
MergeConflictError,
|
|
12
|
+
)
|
|
13
|
+
from .ids import (
|
|
14
|
+
asset_id,
|
|
15
|
+
column_id,
|
|
16
|
+
cte_id,
|
|
17
|
+
leaf_name,
|
|
18
|
+
measure_id,
|
|
19
|
+
model_id,
|
|
20
|
+
normalize_identifier,
|
|
21
|
+
normalize_identifier_part,
|
|
22
|
+
normalize_identifier_parts,
|
|
23
|
+
page_id,
|
|
24
|
+
report_id,
|
|
25
|
+
schema_name,
|
|
26
|
+
split_qualified_identifier,
|
|
27
|
+
table_id,
|
|
28
|
+
visual_id,
|
|
29
|
+
)
|
|
30
|
+
from .interop import (
|
|
31
|
+
AliasMap,
|
|
32
|
+
apply_alias_map,
|
|
33
|
+
normalize_fqn_for_matching,
|
|
34
|
+
resolve_table_match,
|
|
35
|
+
warehouse_table_fqn_candidates,
|
|
36
|
+
warehouse_table_fqn_candidates_from_name,
|
|
37
|
+
)
|
|
38
|
+
from .merge import merge
|
|
39
|
+
from .models import CatalogArtifact, Edge, Evidence, MatchStatus, Node, Warning
|
|
40
|
+
from .serialize import render_json
|
|
41
|
+
|
|
42
|
+
__all__ = [
|
|
43
|
+
"__version__",
|
|
44
|
+
"AliasMap",
|
|
45
|
+
"AliasMapError",
|
|
46
|
+
"MatchStatus",
|
|
47
|
+
"apply_alias_map",
|
|
48
|
+
"asset_id",
|
|
49
|
+
"CatalogArtifact",
|
|
50
|
+
"ClearMetricError",
|
|
51
|
+
"CanonicalIdError",
|
|
52
|
+
"column_id",
|
|
53
|
+
"cte_id",
|
|
54
|
+
"Edge",
|
|
55
|
+
"Evidence",
|
|
56
|
+
"leaf_name",
|
|
57
|
+
"load_table_alias_map",
|
|
58
|
+
"measure_id",
|
|
59
|
+
"merge",
|
|
60
|
+
"normalize_fqn_for_matching",
|
|
61
|
+
"MergeConflictError",
|
|
62
|
+
"model_id",
|
|
63
|
+
"Node",
|
|
64
|
+
"page_id",
|
|
65
|
+
"normalize_identifier",
|
|
66
|
+
"normalize_identifier_part",
|
|
67
|
+
"normalize_identifier_parts",
|
|
68
|
+
"render_json",
|
|
69
|
+
"report_id",
|
|
70
|
+
"resolve_table_match",
|
|
71
|
+
"schema_name",
|
|
72
|
+
"split_qualified_identifier",
|
|
73
|
+
"table_id",
|
|
74
|
+
"visual_id",
|
|
75
|
+
"warehouse_table_fqn_candidates",
|
|
76
|
+
"warehouse_table_fqn_candidates_from_name",
|
|
77
|
+
"Warning",
|
|
78
|
+
]
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
"""Load versioned table alias files for cross-graph matching."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
|
|
7
|
+
from .errors import AliasMapError
|
|
8
|
+
from .interop import AliasMap, normalize_fqn_for_matching
|
|
9
|
+
|
|
10
|
+
_SUPPORTED_VERSION = "1"
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def load_table_alias_map(path: str | Path) -> AliasMap:
|
|
14
|
+
"""
|
|
15
|
+
Load a version-1 alias file into an ``AliasMap``.
|
|
16
|
+
|
|
17
|
+
Expected format::
|
|
18
|
+
|
|
19
|
+
version: 1
|
|
20
|
+
table_aliases:
|
|
21
|
+
salesmart.dbo.orders: orders
|
|
22
|
+
"""
|
|
23
|
+
file_path = Path(path).expanduser().resolve()
|
|
24
|
+
if not file_path.is_file():
|
|
25
|
+
raise AliasMapError(f"Alias file does not exist: {file_path}")
|
|
26
|
+
|
|
27
|
+
version: str | None = None
|
|
28
|
+
aliases: AliasMap = {}
|
|
29
|
+
in_table_aliases = False
|
|
30
|
+
|
|
31
|
+
for line_number, raw_line in enumerate(
|
|
32
|
+
file_path.read_text(encoding="utf-8").splitlines(), start=1
|
|
33
|
+
):
|
|
34
|
+
line = raw_line.strip()
|
|
35
|
+
if not line or line.startswith("#"):
|
|
36
|
+
continue
|
|
37
|
+
|
|
38
|
+
if line.startswith("version:"):
|
|
39
|
+
version = line.split(":", 1)[1].strip()
|
|
40
|
+
if version != _SUPPORTED_VERSION:
|
|
41
|
+
raise AliasMapError(
|
|
42
|
+
f"Unsupported alias file version {version!r} at {file_path}:{line_number}; "
|
|
43
|
+
f"expected {_SUPPORTED_VERSION!r}."
|
|
44
|
+
)
|
|
45
|
+
continue
|
|
46
|
+
|
|
47
|
+
if line == "table_aliases:":
|
|
48
|
+
in_table_aliases = True
|
|
49
|
+
continue
|
|
50
|
+
|
|
51
|
+
if not in_table_aliases:
|
|
52
|
+
raise AliasMapError(
|
|
53
|
+
f"Unexpected content at {file_path}:{line_number}; "
|
|
54
|
+
"expected 'version:' and 'table_aliases:' sections."
|
|
55
|
+
)
|
|
56
|
+
|
|
57
|
+
if ":" not in line:
|
|
58
|
+
raise AliasMapError(
|
|
59
|
+
f"Invalid alias entry at {file_path}:{line_number}; "
|
|
60
|
+
"expected 'source: target' form."
|
|
61
|
+
)
|
|
62
|
+
|
|
63
|
+
source, target = line.split(":", 1)
|
|
64
|
+
source = source.strip()
|
|
65
|
+
target = target.strip()
|
|
66
|
+
if not source or not target:
|
|
67
|
+
raise AliasMapError(
|
|
68
|
+
f"Invalid alias entry at {file_path}:{line_number}; "
|
|
69
|
+
"source and target must be non-empty."
|
|
70
|
+
)
|
|
71
|
+
|
|
72
|
+
normalized_source = normalize_fqn_for_matching(source)
|
|
73
|
+
normalized_target = normalize_fqn_for_matching(target)
|
|
74
|
+
if (
|
|
75
|
+
normalized_source in aliases
|
|
76
|
+
and aliases[normalized_source] != normalized_target
|
|
77
|
+
):
|
|
78
|
+
raise AliasMapError(
|
|
79
|
+
f"Duplicate alias key {normalized_source!r} with conflicting targets "
|
|
80
|
+
f"at {file_path}:{line_number}."
|
|
81
|
+
)
|
|
82
|
+
aliases[normalized_source] = normalized_target
|
|
83
|
+
|
|
84
|
+
if version is None:
|
|
85
|
+
raise AliasMapError(f"Missing 'version:' in alias file: {file_path}")
|
|
86
|
+
if not in_table_aliases:
|
|
87
|
+
raise AliasMapError(
|
|
88
|
+
f"Missing 'table_aliases:' section in alias file: {file_path}"
|
|
89
|
+
)
|
|
90
|
+
|
|
91
|
+
return aliases
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
"""Shared errors for clearmetric-core."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class ClearMetricError(Exception):
|
|
7
|
+
"""Base class for clearmetric-core failures."""
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class CanonicalIdError(ClearMetricError):
|
|
11
|
+
"""Raised when an identifier cannot be normalized into a canonical ID."""
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class MergeConflictError(ClearMetricError):
|
|
15
|
+
"""Raised when artifacts cannot be merged without losing information."""
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class AliasMapError(ClearMetricError):
|
|
19
|
+
"""Raised when a table alias file is invalid or unsupported."""
|