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.
Files changed (198) hide show
  1. pretensor/__init__.py +50 -0
  2. pretensor/benchmark/__init__.py +54 -0
  3. pretensor/benchmark/cli.py +294 -0
  4. pretensor/benchmark/fixtures.py +84 -0
  5. pretensor/benchmark/l1/__init__.py +23 -0
  6. pretensor/benchmark/l1/metrics.py +141 -0
  7. pretensor/benchmark/l1/pipeline.py +188 -0
  8. pretensor/benchmark/l1/runner.py +245 -0
  9. pretensor/benchmark/l2/__init__.py +27 -0
  10. pretensor/benchmark/l2/gold.py +236 -0
  11. pretensor/benchmark/l2/metrics.py +146 -0
  12. pretensor/benchmark/l2/pipeline.py +124 -0
  13. pretensor/benchmark/l2/runner.py +530 -0
  14. pretensor/benchmark/l3/__init__.py +73 -0
  15. pretensor/benchmark/l3/agent.py +316 -0
  16. pretensor/benchmark/l3/db.py +188 -0
  17. pretensor/benchmark/l3/gold.py +85 -0
  18. pretensor/benchmark/l3/llm_client.py +395 -0
  19. pretensor/benchmark/l3/mcp_client.py +357 -0
  20. pretensor/benchmark/l3/pretensor_runner.py +456 -0
  21. pretensor/benchmark/l3/prompt.py +132 -0
  22. pretensor/benchmark/l3/runner.py +358 -0
  23. pretensor/benchmark/l3/sql_equivalence.py +176 -0
  24. pretensor/benchmark/release_gate.py +448 -0
  25. pretensor/benchmark/results.py +298 -0
  26. pretensor/benchmark/runner.py +109 -0
  27. pretensor/cli/__init__.py +1 -0
  28. pretensor/cli/commands/_source_runner.py +147 -0
  29. pretensor/cli/commands/analyze.py +201 -0
  30. pretensor/cli/commands/connections/__init__.py +7 -0
  31. pretensor/cli/commands/connections/add_remove.py +126 -0
  32. pretensor/cli/commands/connections/register.py +12 -0
  33. pretensor/cli/commands/export.py +131 -0
  34. pretensor/cli/commands/index.py +559 -0
  35. pretensor/cli/commands/list.py +76 -0
  36. pretensor/cli/commands/quickstart.py +207 -0
  37. pretensor/cli/commands/reindex.py +646 -0
  38. pretensor/cli/commands/semantic.py +190 -0
  39. pretensor/cli/commands/serve.py +144 -0
  40. pretensor/cli/commands/sync_grants.py +149 -0
  41. pretensor/cli/commands/validate.py +176 -0
  42. pretensor/cli/config_file.py +442 -0
  43. pretensor/cli/constants.py +10 -0
  44. pretensor/cli/dbt_enrichment.py +96 -0
  45. pretensor/cli/main.py +109 -0
  46. pretensor/cli/paths.py +43 -0
  47. pretensor/cli/plugin.py +52 -0
  48. pretensor/config.py +226 -0
  49. pretensor/connectors/__init__.py +29 -0
  50. pretensor/connectors/base.py +165 -0
  51. pretensor/connectors/bigquery.py +468 -0
  52. pretensor/connectors/inspect.py +321 -0
  53. pretensor/connectors/lineage_sqlglot.py +97 -0
  54. pretensor/connectors/models.py +130 -0
  55. pretensor/connectors/mysql.py +402 -0
  56. pretensor/connectors/pg_array_parse.py +53 -0
  57. pretensor/connectors/postgres.py +938 -0
  58. pretensor/connectors/registry.py +93 -0
  59. pretensor/connectors/snapshot.py +244 -0
  60. pretensor/connectors/snowflake.py +908 -0
  61. pretensor/core/__init__.py +1 -0
  62. pretensor/core/builder.py +307 -0
  63. pretensor/core/dsn_crypto.py +51 -0
  64. pretensor/core/graph_schema_manager.py +246 -0
  65. pretensor/core/graph_store.py +1226 -0
  66. pretensor/core/ids.py +101 -0
  67. pretensor/core/portable_export.py +276 -0
  68. pretensor/core/query_runner.py +67 -0
  69. pretensor/core/registry.py +209 -0
  70. pretensor/core/schema.py +473 -0
  71. pretensor/core/secure_io.py +93 -0
  72. pretensor/core/store.py +469 -0
  73. pretensor/enrichment/__init__.py +1 -0
  74. pretensor/enrichment/analyze/__init__.py +0 -0
  75. pretensor/enrichment/analyze/classify.py +49 -0
  76. pretensor/enrichment/analyze/extract_python.py +196 -0
  77. pretensor/enrichment/analyze/parse.py +141 -0
  78. pretensor/enrichment/analyze/pipeline.py +195 -0
  79. pretensor/enrichment/analyze/summary.py +38 -0
  80. pretensor/enrichment/analyze/walker.py +98 -0
  81. pretensor/enrichment/analyze/writers.py +214 -0
  82. pretensor/enrichment/dbt/__init__.py +30 -0
  83. pretensor/enrichment/dbt/lineage.py +100 -0
  84. pretensor/enrichment/dbt/manifest.py +300 -0
  85. pretensor/enrichment/dbt/metadata.py +263 -0
  86. pretensor/enrichment/dbt/pipeline.py +77 -0
  87. pretensor/enrichment/dbt/resolution.py +101 -0
  88. pretensor/enrichment/dbt/signals.py +305 -0
  89. pretensor/entities/__init__.py +27 -0
  90. pretensor/entities/builder.py +63 -0
  91. pretensor/entities/classifier.py +383 -0
  92. pretensor/entities/llm_extract.py +66 -0
  93. pretensor/errors.py +35 -0
  94. pretensor/graph_models/__init__.py +17 -0
  95. pretensor/graph_models/base.py +11 -0
  96. pretensor/graph_models/consumer.py +71 -0
  97. pretensor/graph_models/edge.py +35 -0
  98. pretensor/graph_models/entity.py +21 -0
  99. pretensor/graph_models/node.py +79 -0
  100. pretensor/graph_models/relationship.py +33 -0
  101. pretensor/integrations/__init__.py +42 -0
  102. pretensor/integrations/_base.py +138 -0
  103. pretensor/integrations/google_adk.py +49 -0
  104. pretensor/integrations/langchain.py +55 -0
  105. pretensor/integrations/llamaindex.py +53 -0
  106. pretensor/intelligence/__init__.py +33 -0
  107. pretensor/intelligence/cluster_labeler.py +425 -0
  108. pretensor/intelligence/clustering.py +168 -0
  109. pretensor/intelligence/combining.py +32 -0
  110. pretensor/intelligence/discovery.py +114 -0
  111. pretensor/intelligence/embeddings.py +317 -0
  112. pretensor/intelligence/graph_export.py +200 -0
  113. pretensor/intelligence/heuristic.py +544 -0
  114. pretensor/intelligence/join_paths/__init__.py +130 -0
  115. pretensor/intelligence/join_paths/on_demand.py +516 -0
  116. pretensor/intelligence/join_paths/storage.py +70 -0
  117. pretensor/intelligence/llm_infer.py +78 -0
  118. pretensor/intelligence/llm_runtime.py +62 -0
  119. pretensor/intelligence/metric_templates.py +193 -0
  120. pretensor/intelligence/pipeline.py +364 -0
  121. pretensor/intelligence/role_exemplars.py +263 -0
  122. pretensor/intelligence/schema_classification.py +360 -0
  123. pretensor/intelligence/scoring.py +76 -0
  124. pretensor/intelligence/semantic.py +240 -0
  125. pretensor/intelligence/shadow_alias.py +101 -0
  126. pretensor/intelligence/statistical.py +50 -0
  127. pretensor/intelligence/steps.py +191 -0
  128. pretensor/intelligence/steps_embedding.py +168 -0
  129. pretensor/introspection/__init__.py +6 -0
  130. pretensor/introspection/inspector.py +5 -0
  131. pretensor/introspection/models/__init__.py +0 -0
  132. pretensor/introspection/models/base.py +5 -0
  133. pretensor/introspection/models/config.py +237 -0
  134. pretensor/introspection/models/dsn.py +550 -0
  135. pretensor/introspection/models/plan.py +116 -0
  136. pretensor/introspection/models/schema.py +10 -0
  137. pretensor/introspection/models/semantic.py +121 -0
  138. pretensor/introspection/models/validation.py +116 -0
  139. pretensor/introspection/snapshot.py +46 -0
  140. pretensor/mcp/__init__.py +16 -0
  141. pretensor/mcp/config_json.py +24 -0
  142. pretensor/mcp/payload_types.py +274 -0
  143. pretensor/mcp/resources/__init__.py +17 -0
  144. pretensor/mcp/resources/markdown.py +314 -0
  145. pretensor/mcp/server.py +285 -0
  146. pretensor/mcp/service.py +49 -0
  147. pretensor/mcp/service_context.py +142 -0
  148. pretensor/mcp/service_registry.py +294 -0
  149. pretensor/mcp/store_cache.py +43 -0
  150. pretensor/mcp/tool_registry.py +136 -0
  151. pretensor/mcp/tools/__init__.py +1 -0
  152. pretensor/mcp/tools/_rank.py +244 -0
  153. pretensor/mcp/tools/_timed.py +26 -0
  154. pretensor/mcp/tools/compile_metric.py +144 -0
  155. pretensor/mcp/tools/consumers.py +161 -0
  156. pretensor/mcp/tools/context.py +1121 -0
  157. pretensor/mcp/tools/cypher.py +509 -0
  158. pretensor/mcp/tools/detect_changes.py +254 -0
  159. pretensor/mcp/tools/impact.py +271 -0
  160. pretensor/mcp/tools/list.py +131 -0
  161. pretensor/mcp/tools/schema.py +170 -0
  162. pretensor/mcp/tools/search.py +316 -0
  163. pretensor/mcp/tools/semantic_search.py +282 -0
  164. pretensor/mcp/tools/traverse.py +1027 -0
  165. pretensor/mcp/tools/validate_sql.py +150 -0
  166. pretensor/observability.py +203 -0
  167. pretensor/py.typed +0 -0
  168. pretensor/quickstart/README.md +29 -0
  169. pretensor/quickstart/__init__.py +6 -0
  170. pretensor/quickstart/docker-compose.yml +18 -0
  171. pretensor/quickstart/pagila_data.sql +63 -0
  172. pretensor/quickstart/pagila_ddl.sql +92 -0
  173. pretensor/search/__init__.py +6 -0
  174. pretensor/search/base.py +80 -0
  175. pretensor/search/index.py +435 -0
  176. pretensor/semantic/__init__.py +24 -0
  177. pretensor/semantic/base.py +123 -0
  178. pretensor/semantic/compiler.py +487 -0
  179. pretensor/semantic/yaml_layer.py +180 -0
  180. pretensor/skills/__init__.py +5 -0
  181. pretensor/skills/generator.py +235 -0
  182. pretensor/staleness/__init__.py +15 -0
  183. pretensor/staleness/graph_patcher.py +355 -0
  184. pretensor/staleness/impact_analyzer.py +162 -0
  185. pretensor/staleness/snapshot_store.py +38 -0
  186. pretensor/validation/__init__.py +9 -0
  187. pretensor/validation/query_validator.py +436 -0
  188. pretensor/visibility/__init__.py +23 -0
  189. pretensor/visibility/config.py +126 -0
  190. pretensor/visibility/filter.py +143 -0
  191. pretensor/visibility/kuzu_helpers.py +32 -0
  192. pretensor/visibility/runtime.py +36 -0
  193. pretensor/visibility/sync_grants.py +188 -0
  194. pretensor-0.1.0.dist-info/METADATA +251 -0
  195. pretensor-0.1.0.dist-info/RECORD +198 -0
  196. pretensor-0.1.0.dist-info/WHEEL +4 -0
  197. pretensor-0.1.0.dist-info/entry_points.txt +2 -0
  198. pretensor-0.1.0.dist-info/licenses/LICENSE +21 -0
@@ -0,0 +1,93 @@
1
+ """Registry mapping connection types to connector implementations."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from importlib import import_module
6
+
7
+ from pretensor.connectors.base import BaseConnector
8
+ from pretensor.introspection.models.config import ConnectionConfig, DatabaseType
9
+
10
+ _POSTGRES_EXTRA_HINT = (
11
+ "Install the Postgres extra: pip install 'pretensor[postgres]' "
12
+ "(or pip install psycopg2-binary)."
13
+ )
14
+
15
+ _SNOWFLAKE_EXTRA_HINT = (
16
+ "Install the Snowflake extra: pip install 'pretensor[snowflake]' "
17
+ "(or pip install snowflake-sqlalchemy)."
18
+ )
19
+
20
+ _MYSQL_EXTRA_HINT = (
21
+ "Install the MySQL extra: pip install 'pretensor[mysql]' (or pip install PyMySQL)."
22
+ )
23
+
24
+
25
+ def _postgres_connector_class() -> type[BaseConnector]:
26
+ try:
27
+ # psycopg2 is an optional dependency under the ``postgres`` extra. Import
28
+ # it lazily here (mirroring the other connectors) so a bare install can
29
+ # import pretensor and only fails — with a clear hint — when a Postgres
30
+ # connection is actually attempted.
31
+ import_module("psycopg2")
32
+ except ImportError as exc:
33
+ msg = f"Postgres connector requires psycopg2. {_POSTGRES_EXTRA_HINT}"
34
+ raise ImportError(msg) from exc
35
+ from pretensor.connectors.postgres import PostgresConnector
36
+
37
+ return PostgresConnector
38
+
39
+
40
+ def _snowflake_connector_class() -> type[BaseConnector]:
41
+ try:
42
+ # Optional dep: string path keeps pyright happy when the extra is not installed.
43
+ import_module("snowflake.sqlalchemy")
44
+ except ImportError as exc:
45
+ msg = f"Snowflake connector requires snowflake-sqlalchemy. {_SNOWFLAKE_EXTRA_HINT}"
46
+ raise ImportError(msg) from exc
47
+ from pretensor.connectors.snowflake import SnowflakeConnector
48
+
49
+ return SnowflakeConnector
50
+
51
+
52
+ def _bigquery_connector_class() -> type[BaseConnector]:
53
+ try:
54
+ import_module("google.cloud.bigquery")
55
+ except ImportError as exc:
56
+ from pretensor.connectors.bigquery import _BIGQUERY_EXTRA_HINT
57
+
58
+ msg = (
59
+ f"BigQuery connector requires google-cloud-bigquery. {_BIGQUERY_EXTRA_HINT}"
60
+ )
61
+ raise ImportError(msg) from exc
62
+ from pretensor.connectors.bigquery import BigQueryConnector
63
+
64
+ return BigQueryConnector
65
+
66
+
67
+ def _mysql_connector_class() -> type[BaseConnector]:
68
+ try:
69
+ import_module("pymysql")
70
+ except ImportError as exc:
71
+ msg = f"MySQL connector requires PyMySQL. {_MYSQL_EXTRA_HINT}"
72
+ raise ImportError(msg) from exc
73
+ from pretensor.connectors.mysql import MySQLConnector
74
+
75
+ return MySQLConnector
76
+
77
+
78
+ def get_connector(config: ConnectionConfig) -> BaseConnector:
79
+ """Return a connector instance for the given configuration."""
80
+ if config.type == DatabaseType.POSTGRES:
81
+ cls = _postgres_connector_class()
82
+ return cls(config)
83
+ if config.type == DatabaseType.SNOWFLAKE:
84
+ cls = _snowflake_connector_class()
85
+ return cls(config)
86
+ if config.type == DatabaseType.BIGQUERY:
87
+ cls = _bigquery_connector_class()
88
+ return cls(config)
89
+ if config.type == DatabaseType.MYSQL:
90
+ cls = _mysql_connector_class()
91
+ return cls(config)
92
+ msg = f"No connector registered for database type: {config.type}"
93
+ raise ValueError(msg)
@@ -0,0 +1,244 @@
1
+ """Snapshot diff — compare two schema snapshots for drift detection."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from enum import StrEnum
6
+
7
+ from pretensor.connectors.models import Column, SchemaSnapshot, Table, ViewDependency
8
+ from pretensor.introspection.models.base import PretensorModel
9
+
10
+ __all__ = [
11
+ "ChangeTarget",
12
+ "ChangeType",
13
+ "SchemaChange",
14
+ "diff_snapshots",
15
+ ]
16
+
17
+
18
+ class ChangeType(StrEnum):
19
+ ADDED = "added"
20
+ REMOVED = "removed"
21
+ MODIFIED = "modified"
22
+
23
+
24
+ class ChangeTarget(StrEnum):
25
+ TABLE = "table"
26
+ COLUMN = "column"
27
+ LINEAGE = "lineage"
28
+
29
+
30
+ class SchemaChange(PretensorModel):
31
+ """A single difference between two schema snapshots."""
32
+
33
+ change_type: ChangeType
34
+ target: ChangeTarget
35
+ table_name: str
36
+ schema_name: str
37
+ column_name: str | None = None
38
+ details: str = ""
39
+
40
+
41
+ def diff_snapshots(old: SchemaSnapshot, new: SchemaSnapshot) -> list[SchemaChange]:
42
+ """Compare two snapshots and return a list of changes.
43
+
44
+ Detects added/removed tables and added/removed/modified columns.
45
+ """
46
+ changes: list[SchemaChange] = []
47
+
48
+ old_tables = {(t.schema_name, t.name): t for t in old.tables}
49
+ new_tables = {(t.schema_name, t.name): t for t in new.tables}
50
+
51
+ old_keys = set(old_tables.keys())
52
+ new_keys = set(new_tables.keys())
53
+
54
+ for schema_name, table_name in sorted(new_keys - old_keys):
55
+ changes.append(
56
+ SchemaChange(
57
+ change_type=ChangeType.ADDED,
58
+ target=ChangeTarget.TABLE,
59
+ table_name=table_name,
60
+ schema_name=schema_name,
61
+ details=f"New table with {len(new_tables[(schema_name, table_name)].columns)} columns",
62
+ )
63
+ )
64
+
65
+ for schema_name, table_name in sorted(old_keys - new_keys):
66
+ changes.append(
67
+ SchemaChange(
68
+ change_type=ChangeType.REMOVED,
69
+ target=ChangeTarget.TABLE,
70
+ table_name=table_name,
71
+ schema_name=schema_name,
72
+ )
73
+ )
74
+
75
+ for key in sorted(old_keys & new_keys):
76
+ schema_name, table_name = key
77
+ old_table = old_tables[key]
78
+ new_table = new_tables[key]
79
+
80
+ old_cols = {c.name: c for c in old_table.columns}
81
+ new_cols = {c.name: c for c in new_table.columns}
82
+
83
+ for col_name in sorted(set(new_cols) - set(old_cols)):
84
+ col = new_cols[col_name]
85
+ changes.append(
86
+ SchemaChange(
87
+ change_type=ChangeType.ADDED,
88
+ target=ChangeTarget.COLUMN,
89
+ table_name=table_name,
90
+ schema_name=schema_name,
91
+ column_name=col_name,
92
+ details=f"type={col.data_type}, nullable={col.nullable}",
93
+ )
94
+ )
95
+
96
+ for col_name in sorted(set(old_cols) - set(new_cols)):
97
+ changes.append(
98
+ SchemaChange(
99
+ change_type=ChangeType.REMOVED,
100
+ target=ChangeTarget.COLUMN,
101
+ table_name=table_name,
102
+ schema_name=schema_name,
103
+ column_name=col_name,
104
+ )
105
+ )
106
+
107
+ for col_name in sorted(set(old_cols) & set(new_cols)):
108
+ old_col = old_cols[col_name]
109
+ new_col = new_cols[col_name]
110
+ diffs = _column_diffs(old_col, new_col)
111
+ if diffs:
112
+ changes.append(
113
+ SchemaChange(
114
+ change_type=ChangeType.MODIFIED,
115
+ target=ChangeTarget.COLUMN,
116
+ table_name=table_name,
117
+ schema_name=schema_name,
118
+ column_name=col_name,
119
+ details=", ".join(diffs),
120
+ )
121
+ )
122
+
123
+ table_diffs = _table_diffs(old_table, new_table)
124
+ if table_diffs:
125
+ changes.append(
126
+ SchemaChange(
127
+ change_type=ChangeType.MODIFIED,
128
+ target=ChangeTarget.TABLE,
129
+ table_name=table_name,
130
+ schema_name=schema_name,
131
+ details=", ".join(table_diffs),
132
+ )
133
+ )
134
+
135
+ changes.extend(_lineage_diffs(old.view_dependencies, new.view_dependencies))
136
+
137
+ return changes
138
+
139
+
140
+ def _lineage_key(dep: ViewDependency) -> tuple[str, str, str, str, str]:
141
+ return (
142
+ dep.source_schema,
143
+ dep.source_table,
144
+ dep.target_schema,
145
+ dep.target_table,
146
+ dep.lineage_type,
147
+ )
148
+
149
+
150
+ def _lineage_diffs(
151
+ old_deps: list[ViewDependency], new_deps: list[ViewDependency]
152
+ ) -> list[SchemaChange]:
153
+ """Detect added and removed lineage edges between two snapshots."""
154
+ old_map = {_lineage_key(d): d for d in old_deps}
155
+ new_map = {_lineage_key(d): d for d in new_deps}
156
+ changes: list[SchemaChange] = []
157
+ for key in sorted(set(new_map) - set(old_map)):
158
+ d = new_map[key]
159
+ changes.append(
160
+ SchemaChange(
161
+ change_type=ChangeType.ADDED,
162
+ target=ChangeTarget.LINEAGE,
163
+ schema_name=d.source_schema,
164
+ table_name=d.source_table,
165
+ details=(
166
+ f"{d.lineage_type}: {d.source_schema}.{d.source_table}"
167
+ f" → {d.target_schema}.{d.target_table}"
168
+ ),
169
+ )
170
+ )
171
+ for key in sorted(set(old_map) - set(new_map)):
172
+ d = old_map[key]
173
+ changes.append(
174
+ SchemaChange(
175
+ change_type=ChangeType.REMOVED,
176
+ target=ChangeTarget.LINEAGE,
177
+ schema_name=d.source_schema,
178
+ table_name=d.source_table,
179
+ details=(
180
+ f"{d.lineage_type}: {d.source_schema}.{d.source_table}"
181
+ f" → {d.target_schema}.{d.target_table}"
182
+ ),
183
+ )
184
+ )
185
+ return changes
186
+
187
+
188
+ def _column_diffs(old: Column, new: Column) -> list[str]:
189
+ """Compare two Column objects on structural and catalog-stat fields.
190
+
191
+ ``parent_column`` and ``is_array`` are intentionally excluded: they are
192
+ set once during initial introspection (e.g. BigQuery nested fields) and
193
+ are not expected to change across re-indexes of the same table.
194
+ """
195
+ structural_fields = (
196
+ "data_type",
197
+ "nullable",
198
+ "is_primary_key",
199
+ "is_foreign_key",
200
+ "default_value",
201
+ "comment",
202
+ "is_indexed",
203
+ "check_constraints",
204
+ "ordinal_position",
205
+ "most_common_values",
206
+ "histogram_bounds",
207
+ "stats_correlation",
208
+ )
209
+ diffs: list[str] = []
210
+ for field in structural_fields:
211
+ old_val = getattr(old, field)
212
+ new_val = getattr(new, field)
213
+ if old_val != new_val:
214
+ diffs.append(f"{field}: {old_val!r} → {new_val!r}")
215
+ return diffs
216
+
217
+
218
+ def _table_diffs(old: Table, new: Table) -> list[str]:
219
+ """Compare table-level metadata (not column sets)."""
220
+ diffs: list[str] = []
221
+ for field in (
222
+ "comment",
223
+ "table_type",
224
+ "row_count",
225
+ "seq_scan_count",
226
+ "idx_scan_count",
227
+ "insert_count",
228
+ "update_count",
229
+ "delete_count",
230
+ "is_partitioned",
231
+ "partition_key",
232
+ "grants",
233
+ "access_read_count",
234
+ "access_write_count",
235
+ "days_since_last_access",
236
+ "potentially_unused",
237
+ "table_bytes",
238
+ "clustering_key",
239
+ ):
240
+ old_val = getattr(old, field)
241
+ new_val = getattr(new, field)
242
+ if old_val != new_val:
243
+ diffs.append(f"{field}: {old_val!r} → {new_val!r}")
244
+ return diffs