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.
Files changed (65) hide show
  1. clearmetric_core-0.2.0/PKG-INFO +91 -0
  2. clearmetric_core-0.2.0/README.md +60 -0
  3. clearmetric_core-0.2.0/pyproject.toml +69 -0
  4. clearmetric_core-0.2.0/setup.cfg +4 -0
  5. clearmetric_core-0.2.0/src/clearmetric/cli/__init__.py +157 -0
  6. clearmetric_core-0.2.0/src/clearmetric/cli/__main__.py +8 -0
  7. clearmetric_core-0.2.0/src/clearmetric/core/__init__.py +78 -0
  8. clearmetric_core-0.2.0/src/clearmetric/core/_version.py +3 -0
  9. clearmetric_core-0.2.0/src/clearmetric/core/aliases.py +91 -0
  10. clearmetric_core-0.2.0/src/clearmetric/core/errors.py +19 -0
  11. clearmetric_core-0.2.0/src/clearmetric/core/ids.py +172 -0
  12. clearmetric_core-0.2.0/src/clearmetric/core/interop.py +126 -0
  13. clearmetric_core-0.2.0/src/clearmetric/core/merge.py +147 -0
  14. clearmetric_core-0.2.0/src/clearmetric/core/models.py +68 -0
  15. clearmetric_core-0.2.0/src/clearmetric/core/serialize.py +10 -0
  16. clearmetric_core-0.2.0/src/clearmetric/lineage/__init__.py +36 -0
  17. clearmetric_core-0.2.0/src/clearmetric/lineage/_version.py +5 -0
  18. clearmetric_core-0.2.0/src/clearmetric/lineage/api.py +86 -0
  19. clearmetric_core-0.2.0/src/clearmetric/lineage/build.py +961 -0
  20. clearmetric_core-0.2.0/src/clearmetric/lineage/coverage.py +198 -0
  21. clearmetric_core-0.2.0/src/clearmetric/lineage/errors.py +15 -0
  22. clearmetric_core-0.2.0/src/clearmetric/lineage/graph.py +143 -0
  23. clearmetric_core-0.2.0/src/clearmetric/lineage/loaders.py +249 -0
  24. clearmetric_core-0.2.0/src/clearmetric/lineage/models.py +34 -0
  25. clearmetric_core-0.2.0/src/clearmetric/lineage/render/json.py +10 -0
  26. clearmetric_core-0.2.0/src/clearmetric/lineage/render/mermaid.py +40 -0
  27. clearmetric_core-0.2.0/src/clearmetric/lineage/render/text.py +108 -0
  28. clearmetric_core-0.2.0/src/clearmetric/lineage/sql_analyzer.py +409 -0
  29. clearmetric_core-0.2.0/src/clearmetric/powerbi/__init__.py +28 -0
  30. clearmetric_core-0.2.0/src/clearmetric/powerbi/_version.py +5 -0
  31. clearmetric_core-0.2.0/src/clearmetric/powerbi/api.py +55 -0
  32. clearmetric_core-0.2.0/src/clearmetric/powerbi/build.py +369 -0
  33. clearmetric_core-0.2.0/src/clearmetric/powerbi/discovery.py +109 -0
  34. clearmetric_core-0.2.0/src/clearmetric/powerbi/errors.py +20 -0
  35. clearmetric_core-0.2.0/src/clearmetric/powerbi/m_parser.py +242 -0
  36. clearmetric_core-0.2.0/src/clearmetric/powerbi/models.py +63 -0
  37. clearmetric_core-0.2.0/src/clearmetric/powerbi/native_sql.py +35 -0
  38. clearmetric_core-0.2.0/src/clearmetric/powerbi/render/json.py +15 -0
  39. clearmetric_core-0.2.0/src/clearmetric/powerbi/render/text.py +31 -0
  40. clearmetric_core-0.2.0/src/clearmetric/powerbi/report_parser.py +245 -0
  41. clearmetric_core-0.2.0/src/clearmetric/powerbi/tmdl.py +59 -0
  42. clearmetric_core-0.2.0/src/clearmetric/query/__init__.py +39 -0
  43. clearmetric_core-0.2.0/src/clearmetric/query/_version.py +5 -0
  44. clearmetric_core-0.2.0/src/clearmetric/query/api.py +39 -0
  45. clearmetric_core-0.2.0/src/clearmetric/query/ast_utils.py +81 -0
  46. clearmetric_core-0.2.0/src/clearmetric/query/build.py +263 -0
  47. clearmetric_core-0.2.0/src/clearmetric/query/ctes.py +127 -0
  48. clearmetric_core-0.2.0/src/clearmetric/query/errors.py +15 -0
  49. clearmetric_core-0.2.0/src/clearmetric/query/models.py +93 -0
  50. clearmetric_core-0.2.0/src/clearmetric/query/parser.py +67 -0
  51. clearmetric_core-0.2.0/src/clearmetric/query/relations.py +229 -0
  52. clearmetric_core-0.2.0/src/clearmetric/query/render/__init__.py +1 -0
  53. clearmetric_core-0.2.0/src/clearmetric/query/render/json.py +10 -0
  54. clearmetric_core-0.2.0/src/clearmetric/query/render/text.py +42 -0
  55. clearmetric_core-0.2.0/src/clearmetric_core.egg-info/PKG-INFO +91 -0
  56. clearmetric_core-0.2.0/src/clearmetric_core.egg-info/SOURCES.txt +63 -0
  57. clearmetric_core-0.2.0/src/clearmetric_core.egg-info/dependency_links.txt +1 -0
  58. clearmetric_core-0.2.0/src/clearmetric_core.egg-info/entry_points.txt +2 -0
  59. clearmetric_core-0.2.0/src/clearmetric_core.egg-info/requires.txt +11 -0
  60. clearmetric_core-0.2.0/src/clearmetric_core.egg-info/top_level.txt +1 -0
  61. clearmetric_core-0.2.0/tests/test_aliases.py +63 -0
  62. clearmetric_core-0.2.0/tests/test_contract.py +36 -0
  63. clearmetric_core-0.2.0/tests/test_ids.py +29 -0
  64. clearmetric_core-0.2.0/tests/test_interop.py +89 -0
  65. 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,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -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,8 @@
1
+ """``python -m clearmetric.cli`` entry."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from . import main
6
+
7
+ if __name__ == "__main__":
8
+ raise SystemExit(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,3 @@
1
+ """Package version."""
2
+
3
+ __version__ = "0.2.0"
@@ -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."""