pretensor 0.1.0__py3-none-any.whl
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.
- pretensor/__init__.py +50 -0
- pretensor/benchmark/__init__.py +54 -0
- pretensor/benchmark/cli.py +294 -0
- pretensor/benchmark/fixtures.py +84 -0
- pretensor/benchmark/l1/__init__.py +23 -0
- pretensor/benchmark/l1/metrics.py +141 -0
- pretensor/benchmark/l1/pipeline.py +188 -0
- pretensor/benchmark/l1/runner.py +245 -0
- pretensor/benchmark/l2/__init__.py +27 -0
- pretensor/benchmark/l2/gold.py +236 -0
- pretensor/benchmark/l2/metrics.py +146 -0
- pretensor/benchmark/l2/pipeline.py +124 -0
- pretensor/benchmark/l2/runner.py +530 -0
- pretensor/benchmark/l3/__init__.py +73 -0
- pretensor/benchmark/l3/agent.py +316 -0
- pretensor/benchmark/l3/db.py +188 -0
- pretensor/benchmark/l3/gold.py +85 -0
- pretensor/benchmark/l3/llm_client.py +395 -0
- pretensor/benchmark/l3/mcp_client.py +357 -0
- pretensor/benchmark/l3/pretensor_runner.py +456 -0
- pretensor/benchmark/l3/prompt.py +132 -0
- pretensor/benchmark/l3/runner.py +358 -0
- pretensor/benchmark/l3/sql_equivalence.py +176 -0
- pretensor/benchmark/release_gate.py +448 -0
- pretensor/benchmark/results.py +298 -0
- pretensor/benchmark/runner.py +109 -0
- pretensor/cli/__init__.py +1 -0
- pretensor/cli/commands/_source_runner.py +147 -0
- pretensor/cli/commands/analyze.py +201 -0
- pretensor/cli/commands/connections/__init__.py +7 -0
- pretensor/cli/commands/connections/add_remove.py +126 -0
- pretensor/cli/commands/connections/register.py +12 -0
- pretensor/cli/commands/export.py +131 -0
- pretensor/cli/commands/index.py +559 -0
- pretensor/cli/commands/list.py +76 -0
- pretensor/cli/commands/quickstart.py +207 -0
- pretensor/cli/commands/reindex.py +646 -0
- pretensor/cli/commands/semantic.py +190 -0
- pretensor/cli/commands/serve.py +144 -0
- pretensor/cli/commands/sync_grants.py +149 -0
- pretensor/cli/commands/validate.py +176 -0
- pretensor/cli/config_file.py +442 -0
- pretensor/cli/constants.py +10 -0
- pretensor/cli/dbt_enrichment.py +96 -0
- pretensor/cli/main.py +109 -0
- pretensor/cli/paths.py +43 -0
- pretensor/cli/plugin.py +52 -0
- pretensor/config.py +226 -0
- pretensor/connectors/__init__.py +29 -0
- pretensor/connectors/base.py +165 -0
- pretensor/connectors/bigquery.py +468 -0
- pretensor/connectors/inspect.py +321 -0
- pretensor/connectors/lineage_sqlglot.py +97 -0
- pretensor/connectors/models.py +130 -0
- pretensor/connectors/mysql.py +402 -0
- pretensor/connectors/pg_array_parse.py +53 -0
- pretensor/connectors/postgres.py +938 -0
- pretensor/connectors/registry.py +93 -0
- pretensor/connectors/snapshot.py +244 -0
- pretensor/connectors/snowflake.py +908 -0
- pretensor/core/__init__.py +1 -0
- pretensor/core/builder.py +307 -0
- pretensor/core/dsn_crypto.py +51 -0
- pretensor/core/graph_schema_manager.py +246 -0
- pretensor/core/graph_store.py +1226 -0
- pretensor/core/ids.py +101 -0
- pretensor/core/portable_export.py +276 -0
- pretensor/core/query_runner.py +67 -0
- pretensor/core/registry.py +209 -0
- pretensor/core/schema.py +473 -0
- pretensor/core/secure_io.py +93 -0
- pretensor/core/store.py +469 -0
- pretensor/enrichment/__init__.py +1 -0
- pretensor/enrichment/analyze/__init__.py +0 -0
- pretensor/enrichment/analyze/classify.py +49 -0
- pretensor/enrichment/analyze/extract_python.py +196 -0
- pretensor/enrichment/analyze/parse.py +141 -0
- pretensor/enrichment/analyze/pipeline.py +195 -0
- pretensor/enrichment/analyze/summary.py +38 -0
- pretensor/enrichment/analyze/walker.py +98 -0
- pretensor/enrichment/analyze/writers.py +214 -0
- pretensor/enrichment/dbt/__init__.py +30 -0
- pretensor/enrichment/dbt/lineage.py +100 -0
- pretensor/enrichment/dbt/manifest.py +300 -0
- pretensor/enrichment/dbt/metadata.py +263 -0
- pretensor/enrichment/dbt/pipeline.py +77 -0
- pretensor/enrichment/dbt/resolution.py +101 -0
- pretensor/enrichment/dbt/signals.py +305 -0
- pretensor/entities/__init__.py +27 -0
- pretensor/entities/builder.py +63 -0
- pretensor/entities/classifier.py +383 -0
- pretensor/entities/llm_extract.py +66 -0
- pretensor/errors.py +35 -0
- pretensor/graph_models/__init__.py +17 -0
- pretensor/graph_models/base.py +11 -0
- pretensor/graph_models/consumer.py +71 -0
- pretensor/graph_models/edge.py +35 -0
- pretensor/graph_models/entity.py +21 -0
- pretensor/graph_models/node.py +79 -0
- pretensor/graph_models/relationship.py +33 -0
- pretensor/integrations/__init__.py +42 -0
- pretensor/integrations/_base.py +138 -0
- pretensor/integrations/google_adk.py +49 -0
- pretensor/integrations/langchain.py +55 -0
- pretensor/integrations/llamaindex.py +53 -0
- pretensor/intelligence/__init__.py +33 -0
- pretensor/intelligence/cluster_labeler.py +425 -0
- pretensor/intelligence/clustering.py +168 -0
- pretensor/intelligence/combining.py +32 -0
- pretensor/intelligence/discovery.py +114 -0
- pretensor/intelligence/embeddings.py +317 -0
- pretensor/intelligence/graph_export.py +200 -0
- pretensor/intelligence/heuristic.py +544 -0
- pretensor/intelligence/join_paths/__init__.py +130 -0
- pretensor/intelligence/join_paths/on_demand.py +516 -0
- pretensor/intelligence/join_paths/storage.py +70 -0
- pretensor/intelligence/llm_infer.py +78 -0
- pretensor/intelligence/llm_runtime.py +62 -0
- pretensor/intelligence/metric_templates.py +193 -0
- pretensor/intelligence/pipeline.py +364 -0
- pretensor/intelligence/role_exemplars.py +263 -0
- pretensor/intelligence/schema_classification.py +360 -0
- pretensor/intelligence/scoring.py +76 -0
- pretensor/intelligence/semantic.py +240 -0
- pretensor/intelligence/shadow_alias.py +101 -0
- pretensor/intelligence/statistical.py +50 -0
- pretensor/intelligence/steps.py +191 -0
- pretensor/intelligence/steps_embedding.py +168 -0
- pretensor/introspection/__init__.py +6 -0
- pretensor/introspection/inspector.py +5 -0
- pretensor/introspection/models/__init__.py +0 -0
- pretensor/introspection/models/base.py +5 -0
- pretensor/introspection/models/config.py +237 -0
- pretensor/introspection/models/dsn.py +550 -0
- pretensor/introspection/models/plan.py +116 -0
- pretensor/introspection/models/schema.py +10 -0
- pretensor/introspection/models/semantic.py +121 -0
- pretensor/introspection/models/validation.py +116 -0
- pretensor/introspection/snapshot.py +46 -0
- pretensor/mcp/__init__.py +16 -0
- pretensor/mcp/config_json.py +24 -0
- pretensor/mcp/payload_types.py +274 -0
- pretensor/mcp/resources/__init__.py +17 -0
- pretensor/mcp/resources/markdown.py +314 -0
- pretensor/mcp/server.py +285 -0
- pretensor/mcp/service.py +49 -0
- pretensor/mcp/service_context.py +142 -0
- pretensor/mcp/service_registry.py +294 -0
- pretensor/mcp/store_cache.py +43 -0
- pretensor/mcp/tool_registry.py +136 -0
- pretensor/mcp/tools/__init__.py +1 -0
- pretensor/mcp/tools/_rank.py +244 -0
- pretensor/mcp/tools/_timed.py +26 -0
- pretensor/mcp/tools/compile_metric.py +144 -0
- pretensor/mcp/tools/consumers.py +161 -0
- pretensor/mcp/tools/context.py +1121 -0
- pretensor/mcp/tools/cypher.py +509 -0
- pretensor/mcp/tools/detect_changes.py +254 -0
- pretensor/mcp/tools/impact.py +271 -0
- pretensor/mcp/tools/list.py +131 -0
- pretensor/mcp/tools/schema.py +170 -0
- pretensor/mcp/tools/search.py +316 -0
- pretensor/mcp/tools/semantic_search.py +282 -0
- pretensor/mcp/tools/traverse.py +1027 -0
- pretensor/mcp/tools/validate_sql.py +150 -0
- pretensor/observability.py +203 -0
- pretensor/py.typed +0 -0
- pretensor/quickstart/README.md +29 -0
- pretensor/quickstart/__init__.py +6 -0
- pretensor/quickstart/docker-compose.yml +18 -0
- pretensor/quickstart/pagila_data.sql +63 -0
- pretensor/quickstart/pagila_ddl.sql +92 -0
- pretensor/search/__init__.py +6 -0
- pretensor/search/base.py +80 -0
- pretensor/search/index.py +435 -0
- pretensor/semantic/__init__.py +24 -0
- pretensor/semantic/base.py +123 -0
- pretensor/semantic/compiler.py +487 -0
- pretensor/semantic/yaml_layer.py +180 -0
- pretensor/skills/__init__.py +5 -0
- pretensor/skills/generator.py +235 -0
- pretensor/staleness/__init__.py +15 -0
- pretensor/staleness/graph_patcher.py +355 -0
- pretensor/staleness/impact_analyzer.py +162 -0
- pretensor/staleness/snapshot_store.py +38 -0
- pretensor/validation/__init__.py +9 -0
- pretensor/validation/query_validator.py +436 -0
- pretensor/visibility/__init__.py +23 -0
- pretensor/visibility/config.py +126 -0
- pretensor/visibility/filter.py +143 -0
- pretensor/visibility/kuzu_helpers.py +32 -0
- pretensor/visibility/runtime.py +36 -0
- pretensor/visibility/sync_grants.py +188 -0
- pretensor-0.1.0.dist-info/METADATA +251 -0
- pretensor-0.1.0.dist-info/RECORD +198 -0
- pretensor-0.1.0.dist-info/WHEEL +4 -0
- pretensor-0.1.0.dist-info/entry_points.txt +2 -0
- pretensor-0.1.0.dist-info/licenses/LICENSE +21 -0
pretensor/cli/plugin.py
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
"""CLI plugin discovery via Python entry_points.
|
|
2
|
+
|
|
3
|
+
Plugins declare themselves under the ``pretensor.cli_plugins`` entry-point
|
|
4
|
+
group in their ``pyproject.toml``::
|
|
5
|
+
|
|
6
|
+
[project.entry-points."pretensor.cli_plugins"]
|
|
7
|
+
my-plugin = "my_package.cli_plugin:register"
|
|
8
|
+
|
|
9
|
+
Each entry point must be a callable with the signature::
|
|
10
|
+
|
|
11
|
+
def register(app: typer.Typer) -> None: ...
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
from __future__ import annotations
|
|
15
|
+
|
|
16
|
+
import importlib.metadata
|
|
17
|
+
import logging
|
|
18
|
+
|
|
19
|
+
import typer
|
|
20
|
+
|
|
21
|
+
__all__ = ["discover_cli_plugins"]
|
|
22
|
+
|
|
23
|
+
_ENTRY_POINT_GROUP = "pretensor.cli_plugins"
|
|
24
|
+
|
|
25
|
+
logger = logging.getLogger(__name__)
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def discover_cli_plugins(app: typer.Typer) -> None:
|
|
29
|
+
"""Scan the ``pretensor.cli_plugins`` entry-point group and register commands.
|
|
30
|
+
|
|
31
|
+
Each entry point must resolve to a callable that accepts a single
|
|
32
|
+
``typer.Typer`` argument. Errors during load or registration are logged
|
|
33
|
+
and skipped so a broken plugin never prevents the CLI from starting.
|
|
34
|
+
|
|
35
|
+
Args:
|
|
36
|
+
app: The root Typer application to register plugin commands into.
|
|
37
|
+
"""
|
|
38
|
+
eps = importlib.metadata.entry_points(group=_ENTRY_POINT_GROUP)
|
|
39
|
+
for ep in eps:
|
|
40
|
+
try:
|
|
41
|
+
register = ep.load()
|
|
42
|
+
except Exception:
|
|
43
|
+
logger.warning(
|
|
44
|
+
"pretensor: failed to load CLI plugin %r — skipping", ep.name, exc_info=True
|
|
45
|
+
)
|
|
46
|
+
continue
|
|
47
|
+
try:
|
|
48
|
+
register(app)
|
|
49
|
+
except Exception:
|
|
50
|
+
logger.warning(
|
|
51
|
+
"pretensor: failed to register CLI plugin %r — skipping", ep.name, exc_info=True
|
|
52
|
+
)
|
pretensor/config.py
ADDED
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
"""Graph package configuration (clustering, intelligence defaults)."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from dataclasses import dataclass, field
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
from typing import TYPE_CHECKING
|
|
8
|
+
|
|
9
|
+
if TYPE_CHECKING:
|
|
10
|
+
from pretensor.core.store import KuzuStore
|
|
11
|
+
from pretensor.intelligence.combining import ConfidenceCombiner
|
|
12
|
+
from pretensor.intelligence.scoring import ScorerRegistry
|
|
13
|
+
from pretensor.search.base import BaseSearchIndex
|
|
14
|
+
from pretensor.semantic.base import SemanticLayer
|
|
15
|
+
from pretensor.semantic.yaml_layer import YamlSemanticLayer
|
|
16
|
+
|
|
17
|
+
__all__ = [
|
|
18
|
+
"EmbeddingsConfig",
|
|
19
|
+
"GraphConfig",
|
|
20
|
+
"PretensorConfig",
|
|
21
|
+
"load_semantic_layer",
|
|
22
|
+
]
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
@dataclass(frozen=True, slots=True)
|
|
26
|
+
class EmbeddingsConfig:
|
|
27
|
+
"""Opt-in toggles for the ``[embeddings]`` extra."""
|
|
28
|
+
|
|
29
|
+
index_tables: bool = False
|
|
30
|
+
"""When True and ``[embeddings]`` is installed, ``_EmbeddingIndexStep``
|
|
31
|
+
computes one embedding per :class:`SchemaTable` during intelligence
|
|
32
|
+
indexing. Default ``False`` keeps the null path byte-identical to the
|
|
33
|
+
pre-embedding pipeline output."""
|
|
34
|
+
|
|
35
|
+
cluster_blend: float = 0.0
|
|
36
|
+
"""Weight (in [0.0, 1.0]) for blending embedding cosine similarity into
|
|
37
|
+
existing FK / INFERRED_JOIN edge weights before community detection.
|
|
38
|
+
|
|
39
|
+
For each existing structural edge ``(a, b)`` where both endpoints
|
|
40
|
+
carry an embedding, the edge weight is incremented by
|
|
41
|
+
``cluster_blend * cosine(emb_a, emb_b)``. No new edges are introduced
|
|
42
|
+
from cosine alone — the blend only nudges already-structurally-adjacent
|
|
43
|
+
pairs. Default ``0.0`` keeps cluster membership byte-identical to the
|
|
44
|
+
pre-embedding pipeline output (Invariant #6). A small positive value
|
|
45
|
+
(~0.25) lets domain-related tables with weak lexical overlap drift
|
|
46
|
+
into the same cluster.
|
|
47
|
+
"""
|
|
48
|
+
|
|
49
|
+
join_threshold: float | None = None
|
|
50
|
+
"""Cosine threshold for proposing ``INFERRED_JOIN`` candidates from
|
|
51
|
+
embedding similarity. ``None`` (default) disables the
|
|
52
|
+
embedding scorer entirely → null-path identical. When set (typical
|
|
53
|
+
value ~0.85), an ``EmbeddingRelationshipScorer`` is registered after
|
|
54
|
+
the heuristic scorer; pairs whose cosine ≥ threshold are emitted as
|
|
55
|
+
``status="suggested"`` candidates with ``source="embedding"``. The
|
|
56
|
+
scorer applies a type-family compatibility gate per column pair
|
|
57
|
+
(reusing the heuristic's check) so high-cosine pairs with
|
|
58
|
+
incompatible types are dropped.
|
|
59
|
+
"""
|
|
60
|
+
|
|
61
|
+
role_weight: float = 0.0
|
|
62
|
+
"""Weight for the embedding-based role-classification vote.
|
|
63
|
+
``0.0`` (default) skips the embedding signal entirely → null-path
|
|
64
|
+
identical. When > 0, ``classify_database_tables_async`` embeds each
|
|
65
|
+
table and adds ``role_weight * cosine(table, role_centroid)`` to
|
|
66
|
+
every role's heuristic score; the heuristic remains the primary
|
|
67
|
+
signal. Centroids are computed once per process from a small curated
|
|
68
|
+
exemplar set in ``pretensor.intelligence.role_exemplars``.
|
|
69
|
+
"""
|
|
70
|
+
|
|
71
|
+
def __post_init__(self) -> None:
|
|
72
|
+
# Validate the float knobs at construction time so misuse surfaces
|
|
73
|
+
# close to its source rather than mid-pipeline. All three numeric
|
|
74
|
+
# toggles are checked here for one consistent contract on the
|
|
75
|
+
# dataclass; ``EmbeddingRelationshipScorer.__init__`` keeps its own
|
|
76
|
+
# belt-and-braces check for callers that construct the scorer
|
|
77
|
+
# directly with an out-of-band threshold.
|
|
78
|
+
if not 0.0 <= self.cluster_blend <= 1.0:
|
|
79
|
+
msg = (
|
|
80
|
+
f"EmbeddingsConfig.cluster_blend must be in [0.0, 1.0]; "
|
|
81
|
+
f"got {self.cluster_blend!r}"
|
|
82
|
+
)
|
|
83
|
+
raise ValueError(msg)
|
|
84
|
+
if not 0.0 <= self.role_weight <= 1.0:
|
|
85
|
+
msg = (
|
|
86
|
+
f"EmbeddingsConfig.role_weight must be in [0.0, 1.0]; "
|
|
87
|
+
f"got {self.role_weight!r}"
|
|
88
|
+
)
|
|
89
|
+
raise ValueError(msg)
|
|
90
|
+
if self.join_threshold is not None and not (
|
|
91
|
+
0.0 <= self.join_threshold <= 1.0
|
|
92
|
+
):
|
|
93
|
+
msg = (
|
|
94
|
+
f"EmbeddingsConfig.join_threshold must be in [0.0, 1.0] "
|
|
95
|
+
f"or None; got {self.join_threshold!r}"
|
|
96
|
+
)
|
|
97
|
+
raise ValueError(msg)
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
@dataclass(frozen=True, slots=True)
|
|
101
|
+
class GraphConfig:
|
|
102
|
+
"""Tunable parameters for graph intelligence (clustering, join paths)."""
|
|
103
|
+
|
|
104
|
+
stale_index_warning_days: int = 7
|
|
105
|
+
"""Warn in MCP responses when ``last_indexed`` is older than this many days."""
|
|
106
|
+
|
|
107
|
+
clustering_resolution_override: float | None = None
|
|
108
|
+
"""If set, skip automatic resolution heuristic and use this value for Leiden."""
|
|
109
|
+
|
|
110
|
+
join_path_max_depth: int = 4
|
|
111
|
+
"""Maximum hop count for precomputed intra-cluster join paths."""
|
|
112
|
+
|
|
113
|
+
min_cluster_size_merge: int = 3
|
|
114
|
+
"""Clusters with fewer tables are merged into a neighbor cluster."""
|
|
115
|
+
|
|
116
|
+
collapse_shadow_aliases: bool = True
|
|
117
|
+
"""Treat single-source views (``table_type='view'`` with exactly one
|
|
118
|
+
incoming ``LINEAGE`` edge) as transparent aliases of their base table.
|
|
119
|
+
When enabled, intelligence consumers (clustering, INFERRED_JOIN, impact,
|
|
120
|
+
traverse, context) suppress or pass through these aliases."""
|
|
121
|
+
|
|
122
|
+
lineage_in_max_for_alias: int = 1
|
|
123
|
+
"""Maximum incoming LINEAGE edge count for a view to be considered a
|
|
124
|
+
shadow alias. The default of 1 matches pure 1:1 projections."""
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
def _default_scorer_registry() -> ScorerRegistry:
|
|
128
|
+
from pretensor.intelligence.heuristic import HeuristicScorer
|
|
129
|
+
from pretensor.intelligence.scoring import ScorerRegistry
|
|
130
|
+
|
|
131
|
+
return ScorerRegistry([HeuristicScorer()])
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
def _default_combiner() -> ConfidenceCombiner:
|
|
135
|
+
from pretensor.intelligence.combining import MaxScoreCombiner
|
|
136
|
+
|
|
137
|
+
return MaxScoreCombiner()
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
def _default_search_index_cls() -> type[BaseSearchIndex]:
|
|
141
|
+
from pretensor.search.index import KeywordSearchIndex
|
|
142
|
+
|
|
143
|
+
return KeywordSearchIndex
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
def _default_semantic_layer() -> SemanticLayer:
|
|
147
|
+
from pretensor.semantic.base import NullSemanticLayer
|
|
148
|
+
|
|
149
|
+
return NullSemanticLayer()
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
@dataclass
|
|
153
|
+
class PretensorConfig:
|
|
154
|
+
"""Central pluggable configuration for the Pretensor graph system.
|
|
155
|
+
|
|
156
|
+
Bundles all tunable and swappable components so that downstream packages can ship a
|
|
157
|
+
``PretensorConfig`` subclass with its own implementations
|
|
158
|
+
without patching individual files.
|
|
159
|
+
|
|
160
|
+
All fields default to the OSS implementations, so constructing
|
|
161
|
+
``PretensorConfig()`` with no arguments is always valid for basic use.
|
|
162
|
+
|
|
163
|
+
Attributes:
|
|
164
|
+
graph: Graph intelligence tuning parameters (clustering, join paths).
|
|
165
|
+
embeddings: Opt-in toggles for the ``[embeddings]`` extra.
|
|
166
|
+
Default: ``EmbeddingsConfig()`` (all toggles off).
|
|
167
|
+
scorer_registry: Ordered registry of relationship scorers used by discovery.
|
|
168
|
+
Default: ``ScorerRegistry([HeuristicScorer()])``.
|
|
169
|
+
combiner: Strategy for merging scored relationship candidates.
|
|
170
|
+
Default: ``MaxScoreCombiner()``.
|
|
171
|
+
search_index_cls: Class used to build or load the keyword/vector search index.
|
|
172
|
+
Default: ``KeywordSearchIndex``.
|
|
173
|
+
semantic_layer: Semantic enrichment layer for entities, metrics, and dimensions.
|
|
174
|
+
Default: ``NullSemanticLayer()`` (no-op for OSS).
|
|
175
|
+
"""
|
|
176
|
+
|
|
177
|
+
graph: GraphConfig = field(default_factory=GraphConfig)
|
|
178
|
+
embeddings: EmbeddingsConfig = field(default_factory=EmbeddingsConfig)
|
|
179
|
+
scorer_registry: ScorerRegistry = field(default_factory=_default_scorer_registry)
|
|
180
|
+
combiner: ConfidenceCombiner = field(default_factory=_default_combiner)
|
|
181
|
+
search_index_cls: type[BaseSearchIndex] = field(
|
|
182
|
+
default_factory=_default_search_index_cls
|
|
183
|
+
)
|
|
184
|
+
semantic_layer: SemanticLayer = field(default_factory=_default_semantic_layer)
|
|
185
|
+
|
|
186
|
+
|
|
187
|
+
def load_semantic_layer(
|
|
188
|
+
yaml_path: Path,
|
|
189
|
+
*,
|
|
190
|
+
store: KuzuStore,
|
|
191
|
+
database_key: str,
|
|
192
|
+
dialect: str = "postgres",
|
|
193
|
+
) -> YamlSemanticLayer:
|
|
194
|
+
"""Read a YAML file and return a ready-to-use :class:`YamlSemanticLayer`.
|
|
195
|
+
|
|
196
|
+
The YAML must match the
|
|
197
|
+
:class:`pretensor.introspection.models.semantic.SemanticLayer` schema.
|
|
198
|
+
The returned layer uses ``store`` for graph lookups (metric compilation,
|
|
199
|
+
query validation, impact) scoped to ``database_key``.
|
|
200
|
+
|
|
201
|
+
Args:
|
|
202
|
+
yaml_path: Filesystem path to the semantic layer YAML.
|
|
203
|
+
store: Open Kuzu store for the same ``database_key``.
|
|
204
|
+
database_key: Logical database key as recorded on ``SchemaTable``.
|
|
205
|
+
dialect: sqlglot dialect for SQL parsing (default PostgreSQL).
|
|
206
|
+
|
|
207
|
+
Returns:
|
|
208
|
+
A :class:`YamlSemanticLayer` instance.
|
|
209
|
+
|
|
210
|
+
Raises:
|
|
211
|
+
FileNotFoundError: If ``yaml_path`` does not exist.
|
|
212
|
+
pydantic.ValidationError: If the YAML fails schema validation.
|
|
213
|
+
"""
|
|
214
|
+
from pretensor.introspection.models.semantic import (
|
|
215
|
+
SemanticLayer as SemanticLayerModel,
|
|
216
|
+
)
|
|
217
|
+
from pretensor.semantic.yaml_layer import YamlSemanticLayer
|
|
218
|
+
|
|
219
|
+
text = yaml_path.read_text(encoding="utf-8")
|
|
220
|
+
model = SemanticLayerModel.from_yaml(text)
|
|
221
|
+
return YamlSemanticLayer(
|
|
222
|
+
model,
|
|
223
|
+
store=store,
|
|
224
|
+
database_key=database_key,
|
|
225
|
+
dialect=dialect,
|
|
226
|
+
)
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
"""Connector-layer schema types, introspection, and snapshot diffing."""
|
|
2
|
+
|
|
3
|
+
from pretensor.connectors.inspect import inspect
|
|
4
|
+
from pretensor.connectors.models import (
|
|
5
|
+
Column,
|
|
6
|
+
ConnectionConfig,
|
|
7
|
+
ForeignKey,
|
|
8
|
+
SchemaSnapshot,
|
|
9
|
+
Table,
|
|
10
|
+
)
|
|
11
|
+
from pretensor.connectors.snapshot import (
|
|
12
|
+
ChangeTarget,
|
|
13
|
+
ChangeType,
|
|
14
|
+
SchemaChange,
|
|
15
|
+
diff_snapshots,
|
|
16
|
+
)
|
|
17
|
+
|
|
18
|
+
__all__ = [
|
|
19
|
+
"ChangeTarget",
|
|
20
|
+
"ChangeType",
|
|
21
|
+
"Column",
|
|
22
|
+
"ConnectionConfig",
|
|
23
|
+
"ForeignKey",
|
|
24
|
+
"SchemaChange",
|
|
25
|
+
"SchemaSnapshot",
|
|
26
|
+
"Table",
|
|
27
|
+
"diff_snapshots",
|
|
28
|
+
"inspect",
|
|
29
|
+
]
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
"""Abstract connector interface for database introspection.
|
|
2
|
+
|
|
3
|
+
Every database connector implements BaseConnector. All database-specific
|
|
4
|
+
imports and logic live inside the concrete connector files — nothing
|
|
5
|
+
above the connector layer knows which database produced the data.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
from abc import ABC, abstractmethod
|
|
11
|
+
from typing import Any
|
|
12
|
+
|
|
13
|
+
from pydantic import Field
|
|
14
|
+
|
|
15
|
+
from pretensor.connectors.models import ViewDependency
|
|
16
|
+
from pretensor.introspection.models.base import PretensorModel
|
|
17
|
+
from pretensor.introspection.models.config import ConnectionConfig, SchemaFilter
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class TableInfo(PretensorModel):
|
|
21
|
+
"""Table metadata returned by a connector (no column details)."""
|
|
22
|
+
|
|
23
|
+
name: str
|
|
24
|
+
schema_name: str
|
|
25
|
+
row_count: int | None = None
|
|
26
|
+
row_count_source: str | None = Field(
|
|
27
|
+
default=None,
|
|
28
|
+
description=(
|
|
29
|
+
"Provenance of `row_count`: 'stat' (catalog statistic), "
|
|
30
|
+
"'view_count' (live SELECT COUNT(*) on a view), "
|
|
31
|
+
"'view_timeout' (view count exceeded the connector timeout — "
|
|
32
|
+
"row_count = -1)."
|
|
33
|
+
),
|
|
34
|
+
)
|
|
35
|
+
comment: str | None = None
|
|
36
|
+
table_type: str | None = None
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
class ColumnInfo(PretensorModel):
|
|
40
|
+
"""Column metadata returned by a connector (no stats)."""
|
|
41
|
+
|
|
42
|
+
name: str
|
|
43
|
+
data_type: str
|
|
44
|
+
nullable: bool = True
|
|
45
|
+
is_primary_key: bool = False
|
|
46
|
+
default_value: str | None = None
|
|
47
|
+
comment: str | None = None
|
|
48
|
+
is_indexed: bool = False
|
|
49
|
+
check_constraints: list[str] = Field(default_factory=list)
|
|
50
|
+
ordinal_position: int | None = None
|
|
51
|
+
column_cardinality: int | None = None
|
|
52
|
+
index_type: str | None = None
|
|
53
|
+
index_is_unique: bool | None = None
|
|
54
|
+
parent_column: str | None = None
|
|
55
|
+
is_array: bool = False
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
class ForeignKeyInfo(PretensorModel):
|
|
59
|
+
"""Foreign key relationship returned by a connector."""
|
|
60
|
+
|
|
61
|
+
constraint_name: str | None = None
|
|
62
|
+
source_schema: str
|
|
63
|
+
source_table: str
|
|
64
|
+
source_column: str
|
|
65
|
+
target_schema: str
|
|
66
|
+
target_table: str
|
|
67
|
+
target_column: str
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
class ColumnStats(PretensorModel):
|
|
71
|
+
"""Statistical metadata for a single column."""
|
|
72
|
+
|
|
73
|
+
distinct_count: int | None = None
|
|
74
|
+
min_value: str | None = None
|
|
75
|
+
max_value: str | None = None
|
|
76
|
+
null_percentage: float | None = None
|
|
77
|
+
sample_distinct_values: list[str] | None = None
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
class TableGrant(PretensorModel):
|
|
81
|
+
"""SELECT grant on a table for an effective grantee (role or login)."""
|
|
82
|
+
|
|
83
|
+
grantee: str
|
|
84
|
+
schema_name: str
|
|
85
|
+
table_name: str
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
class BaseConnector(ABC):
|
|
89
|
+
"""Abstract interface that every database connector must implement.
|
|
90
|
+
|
|
91
|
+
Usage as a context manager is preferred::
|
|
92
|
+
|
|
93
|
+
with PostgresConnector(config) as conn:
|
|
94
|
+
tables = conn.get_tables()
|
|
95
|
+
"""
|
|
96
|
+
|
|
97
|
+
def __init__(self, config: ConnectionConfig) -> None:
|
|
98
|
+
self.config = config
|
|
99
|
+
|
|
100
|
+
def __enter__(self) -> BaseConnector:
|
|
101
|
+
self.connect()
|
|
102
|
+
return self
|
|
103
|
+
|
|
104
|
+
def __exit__(self, *exc: Any) -> None:
|
|
105
|
+
self.disconnect()
|
|
106
|
+
|
|
107
|
+
@abstractmethod
|
|
108
|
+
def connect(self) -> None: ...
|
|
109
|
+
|
|
110
|
+
@abstractmethod
|
|
111
|
+
def disconnect(self) -> None: ...
|
|
112
|
+
|
|
113
|
+
@abstractmethod
|
|
114
|
+
def get_tables(
|
|
115
|
+
self, schema_filter: SchemaFilter | None = None
|
|
116
|
+
) -> list[TableInfo]: ...
|
|
117
|
+
|
|
118
|
+
@abstractmethod
|
|
119
|
+
def get_columns(self, table_name: str, schema_name: str) -> list[ColumnInfo]: ...
|
|
120
|
+
|
|
121
|
+
@abstractmethod
|
|
122
|
+
def get_foreign_keys(self) -> list[ForeignKeyInfo]: ...
|
|
123
|
+
|
|
124
|
+
@abstractmethod
|
|
125
|
+
def get_table_row_count(self, table_name: str, schema_name: str) -> int: ...
|
|
126
|
+
|
|
127
|
+
@abstractmethod
|
|
128
|
+
def get_column_stats(
|
|
129
|
+
self, table_name: str, column_name: str, schema_name: str
|
|
130
|
+
) -> ColumnStats: ...
|
|
131
|
+
|
|
132
|
+
@abstractmethod
|
|
133
|
+
def execute_query(self, sql: str) -> list[dict[str, Any]]: ...
|
|
134
|
+
|
|
135
|
+
def load_deep_catalog(
|
|
136
|
+
self, schema_filter: SchemaFilter
|
|
137
|
+
) -> tuple[
|
|
138
|
+
dict[tuple[str, str], dict[str, Any]],
|
|
139
|
+
dict[tuple[str, str, str], dict[str, Any]],
|
|
140
|
+
]:
|
|
141
|
+
"""Optional bulk catalog enrichment (usage, stats, grants). Default: none.
|
|
142
|
+
|
|
143
|
+
Callers treat this as best-effort: failures must not block indexing (log and
|
|
144
|
+
continue without enrichment).
|
|
145
|
+
"""
|
|
146
|
+
_ = schema_filter
|
|
147
|
+
return {}, {}
|
|
148
|
+
|
|
149
|
+
def load_view_dependencies(self, schema_filter: SchemaFilter) -> list[ViewDependency]:
|
|
150
|
+
"""Table-level lineage from views, triggers, or warehouse objects. Default: none."""
|
|
151
|
+
_ = schema_filter
|
|
152
|
+
return []
|
|
153
|
+
|
|
154
|
+
def get_table_grants(
|
|
155
|
+
self, schema_filter: SchemaFilter | None = None
|
|
156
|
+
) -> list[TableGrant]:
|
|
157
|
+
"""SELECT table grants, including connector-specific expansion (e.g. role inheritance).
|
|
158
|
+
|
|
159
|
+
Default: no grant introspection (empty list).
|
|
160
|
+
|
|
161
|
+
Callers treat this as best-effort: failures must not block indexing (log and
|
|
162
|
+
continue without grants), same contract as :meth:`load_deep_catalog`.
|
|
163
|
+
"""
|
|
164
|
+
_ = schema_filter
|
|
165
|
+
return []
|