pretensor 0.1.0a0__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/cli/__init__.py +1 -0
- pretensor/cli/commands/connections/__init__.py +7 -0
- pretensor/cli/commands/connections/add_remove.py +122 -0
- pretensor/cli/commands/connections/register.py +12 -0
- pretensor/cli/commands/export.py +120 -0
- pretensor/cli/commands/index.py +551 -0
- pretensor/cli/commands/list.py +65 -0
- pretensor/cli/commands/quickstart.py +171 -0
- pretensor/cli/commands/reindex.py +581 -0
- pretensor/cli/commands/semantic.py +190 -0
- pretensor/cli/commands/serve.py +129 -0
- pretensor/cli/commands/sync_grants.py +149 -0
- pretensor/cli/commands/validate.py +156 -0
- pretensor/cli/config_file.py +412 -0
- pretensor/cli/constants.py +10 -0
- pretensor/cli/dbt_enrichment.py +96 -0
- pretensor/cli/main.py +81 -0
- pretensor/cli/paths.py +43 -0
- pretensor/cli/plugin.py +52 -0
- pretensor/config.py +145 -0
- pretensor/connectors/SKILL.md +16 -0
- pretensor/connectors/__init__.py +29 -0
- pretensor/connectors/base.py +165 -0
- pretensor/connectors/bigquery.py +463 -0
- pretensor/connectors/inspect.py +321 -0
- pretensor/connectors/lineage_sqlglot.py +91 -0
- pretensor/connectors/models.py +130 -0
- pretensor/connectors/pg_array_parse.py +53 -0
- pretensor/connectors/postgres.py +921 -0
- pretensor/connectors/registry.py +51 -0
- pretensor/connectors/snapshot.py +244 -0
- pretensor/connectors/snowflake.py +840 -0
- pretensor/core/__init__.py +1 -0
- pretensor/core/builder.py +268 -0
- pretensor/core/dsn_crypto.py +40 -0
- pretensor/core/ids.py +68 -0
- pretensor/core/portable_export.py +253 -0
- pretensor/core/registry.py +204 -0
- pretensor/core/schema.py +368 -0
- pretensor/core/store.py +1343 -0
- pretensor/enrichment/__init__.py +1 -0
- pretensor/enrichment/dbt/__init__.py +30 -0
- pretensor/enrichment/dbt/lineage.py +100 -0
- pretensor/enrichment/dbt/manifest.py +298 -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 +359 -0
- pretensor/entities/llm_extract.py +66 -0
- pretensor/graph_models/__init__.py +14 -0
- pretensor/graph_models/base.py +11 -0
- pretensor/graph_models/edge.py +35 -0
- pretensor/graph_models/entity.py +21 -0
- pretensor/graph_models/node.py +75 -0
- pretensor/graph_models/relationship.py +27 -0
- pretensor/intelligence/__init__.py +33 -0
- pretensor/intelligence/cluster_labeler.py +335 -0
- pretensor/intelligence/clustering.py +162 -0
- pretensor/intelligence/combining.py +32 -0
- pretensor/intelligence/discovery.py +114 -0
- pretensor/intelligence/embeddings.py +198 -0
- pretensor/intelligence/graph_export.py +139 -0
- pretensor/intelligence/heuristic.py +540 -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 +60 -0
- pretensor/intelligence/metric_templates.py +193 -0
- pretensor/intelligence/pipeline.py +254 -0
- pretensor/intelligence/schema_classification.py +306 -0
- pretensor/intelligence/scoring.py +66 -0
- pretensor/intelligence/shadow_alias.py +101 -0
- pretensor/intelligence/statistical.py +50 -0
- pretensor/intelligence/steps.py +161 -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 +236 -0
- pretensor/introspection/models/dsn.py +442 -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 +248 -0
- pretensor/mcp/resources/__init__.py +19 -0
- pretensor/mcp/resources/markdown.py +420 -0
- pretensor/mcp/server.py +723 -0
- pretensor/mcp/service.py +47 -0
- pretensor/mcp/service_context.py +126 -0
- pretensor/mcp/service_registry.py +242 -0
- pretensor/mcp/tool_registry.py +126 -0
- pretensor/mcp/tools/__init__.py +1 -0
- pretensor/mcp/tools/compile_metric.py +87 -0
- pretensor/mcp/tools/context.py +894 -0
- pretensor/mcp/tools/cypher.py +333 -0
- pretensor/mcp/tools/detect_changes.py +226 -0
- pretensor/mcp/tools/impact.py +165 -0
- pretensor/mcp/tools/list.py +110 -0
- pretensor/mcp/tools/schema.py +128 -0
- pretensor/mcp/tools/search.py +108 -0
- pretensor/mcp/tools/traverse.py +753 -0
- pretensor/mcp/tools/validate_sql.py +74 -0
- pretensor/observability.py +203 -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 +346 -0
- pretensor/semantic/yaml_layer.py +182 -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.0a0.dist-info/METADATA +175 -0
- pretensor-0.1.0a0.dist-info/RECORD +138 -0
- pretensor-0.1.0a0.dist-info/WHEEL +4 -0
- pretensor-0.1.0a0.dist-info/entry_points.txt +2 -0
- pretensor-0.1.0a0.dist-info/licenses/LICENSE +21 -0
pretensor/__init__.py
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
"""Pretensor Graph — schema knowledge graph backed by Kuzu.
|
|
2
|
+
|
|
3
|
+
Heavy imports (e.g. :class:`GraphBuilder`) are lazy so ``import pretensor``
|
|
4
|
+
works in tests that only touch the registry.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
# pyright: reportUnsupportedDunderAll=false
|
|
8
|
+
# Names in __all__ are provided via __getattr__; Pyright does not model that.
|
|
9
|
+
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
|
|
12
|
+
from typing import Any
|
|
13
|
+
|
|
14
|
+
__all__ = [
|
|
15
|
+
"GraphBuilder",
|
|
16
|
+
"GraphEdge",
|
|
17
|
+
"GraphNode",
|
|
18
|
+
"GraphRegistry",
|
|
19
|
+
"KuzuStore",
|
|
20
|
+
"RelationshipCandidate",
|
|
21
|
+
]
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def __getattr__(name: str) -> Any:
|
|
25
|
+
if name == "GraphBuilder":
|
|
26
|
+
from pretensor.core.builder import GraphBuilder
|
|
27
|
+
|
|
28
|
+
return GraphBuilder
|
|
29
|
+
if name == "GraphRegistry":
|
|
30
|
+
from pretensor.core.registry import GraphRegistry
|
|
31
|
+
|
|
32
|
+
return GraphRegistry
|
|
33
|
+
if name == "KuzuStore":
|
|
34
|
+
from pretensor.core.store import KuzuStore
|
|
35
|
+
|
|
36
|
+
return KuzuStore
|
|
37
|
+
if name == "GraphNode":
|
|
38
|
+
from pretensor.graph_models.node import GraphNode
|
|
39
|
+
|
|
40
|
+
return GraphNode
|
|
41
|
+
if name == "GraphEdge":
|
|
42
|
+
from pretensor.graph_models.edge import GraphEdge
|
|
43
|
+
|
|
44
|
+
return GraphEdge
|
|
45
|
+
if name == "RelationshipCandidate":
|
|
46
|
+
from pretensor.graph_models.relationship import RelationshipCandidate
|
|
47
|
+
|
|
48
|
+
return RelationshipCandidate
|
|
49
|
+
msg = f"module {__name__!r} has no attribute {name!r}"
|
|
50
|
+
raise AttributeError(msg)
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Command-line interface for Pretensor Graph."""
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
"""``add`` and ``remove`` cross-DB registry commands."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
|
|
7
|
+
import typer
|
|
8
|
+
from rich.console import Console
|
|
9
|
+
|
|
10
|
+
from pretensor.cli import constants as cli_constants
|
|
11
|
+
from pretensor.cli.config_file import (
|
|
12
|
+
get_cli_config,
|
|
13
|
+
resolve_optional_str_option,
|
|
14
|
+
resolve_path_option,
|
|
15
|
+
)
|
|
16
|
+
from pretensor.cli.paths import default_connection_name, unified_graph_path
|
|
17
|
+
from pretensor.core.dsn_crypto import DSNEncryptor
|
|
18
|
+
from pretensor.core.registry import GraphRegistry
|
|
19
|
+
from pretensor.introspection.models.dsn import (
|
|
20
|
+
connection_config_from_url,
|
|
21
|
+
registry_dialect_for,
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def _encryptor_for(state_dir: Path) -> DSNEncryptor:
|
|
26
|
+
return DSNEncryptor(state_dir / "keystore")
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def register_add_remove_commands(app: typer.Typer) -> None:
|
|
30
|
+
@app.command("add")
|
|
31
|
+
def add_command(
|
|
32
|
+
dsn: str = typer.Argument(
|
|
33
|
+
..., help="Database DSN URL (postgresql:// or snowflake://)."
|
|
34
|
+
),
|
|
35
|
+
name: str | None = typer.Option(
|
|
36
|
+
None,
|
|
37
|
+
"--name",
|
|
38
|
+
"-n",
|
|
39
|
+
help="Logical connection name (default: DB name from DSN).",
|
|
40
|
+
),
|
|
41
|
+
state_dir: Path = typer.Option(
|
|
42
|
+
cli_constants.DEFAULT_STATE_DIR,
|
|
43
|
+
"--state-dir",
|
|
44
|
+
help="State directory for registry and graphs.",
|
|
45
|
+
file_okay=False,
|
|
46
|
+
dir_okay=True,
|
|
47
|
+
writable=True,
|
|
48
|
+
resolve_path=True,
|
|
49
|
+
),
|
|
50
|
+
ctx: typer.Context = typer.Option(None, hidden=True),
|
|
51
|
+
) -> None:
|
|
52
|
+
"""Register a DSN (encrypted); run `pretensor index` to build the graph."""
|
|
53
|
+
cli_config = get_cli_config(ctx)
|
|
54
|
+
state_dir = resolve_path_option(
|
|
55
|
+
ctx,
|
|
56
|
+
param_name="state_dir",
|
|
57
|
+
cli_value=state_dir,
|
|
58
|
+
config_value=cli_config.state_dir,
|
|
59
|
+
)
|
|
60
|
+
name = resolve_optional_str_option(
|
|
61
|
+
ctx,
|
|
62
|
+
param_name="name",
|
|
63
|
+
cli_value=name,
|
|
64
|
+
config_value=cli_config.connection_defaults.name,
|
|
65
|
+
)
|
|
66
|
+
console = Console()
|
|
67
|
+
connection_name = name or default_connection_name(dsn)
|
|
68
|
+
enc = _encryptor_for(state_dir)
|
|
69
|
+
reg_path = state_dir / cli_constants.REGISTRY_FILENAME
|
|
70
|
+
reg = GraphRegistry(reg_path).load()
|
|
71
|
+
graph_path = unified_graph_path(state_dir)
|
|
72
|
+
cfg = connection_config_from_url(dsn, connection_name)
|
|
73
|
+
reg.upsert(
|
|
74
|
+
connection_name=connection_name,
|
|
75
|
+
database=cfg.database or connection_name,
|
|
76
|
+
dsn=dsn,
|
|
77
|
+
graph_path=graph_path,
|
|
78
|
+
unified_graph_path=graph_path,
|
|
79
|
+
encrypt_dsn=True,
|
|
80
|
+
encryptor=enc,
|
|
81
|
+
indexed_at=None,
|
|
82
|
+
dialect=registry_dialect_for(cfg.type),
|
|
83
|
+
)
|
|
84
|
+
reg.save()
|
|
85
|
+
console.print(
|
|
86
|
+
f"[green]Registered[/green] connection {connection_name!r} (DSN encrypted)."
|
|
87
|
+
)
|
|
88
|
+
console.print(
|
|
89
|
+
f"Run: pretensor index {dsn!r} --name {connection_name} --unified"
|
|
90
|
+
)
|
|
91
|
+
|
|
92
|
+
@app.command("remove")
|
|
93
|
+
def remove_command(
|
|
94
|
+
name: str = typer.Argument(
|
|
95
|
+
..., help="Connection name to remove from registry."
|
|
96
|
+
),
|
|
97
|
+
state_dir: Path = typer.Option(
|
|
98
|
+
cli_constants.DEFAULT_STATE_DIR,
|
|
99
|
+
"--state-dir",
|
|
100
|
+
file_okay=False,
|
|
101
|
+
dir_okay=True,
|
|
102
|
+
resolve_path=True,
|
|
103
|
+
),
|
|
104
|
+
ctx: typer.Context = typer.Option(None, hidden=True),
|
|
105
|
+
) -> None:
|
|
106
|
+
"""Remove a connection from the registry (does not delete the unified graph file)."""
|
|
107
|
+
cli_config = get_cli_config(ctx)
|
|
108
|
+
state_dir = resolve_path_option(
|
|
109
|
+
ctx,
|
|
110
|
+
param_name="state_dir",
|
|
111
|
+
cli_value=state_dir,
|
|
112
|
+
config_value=cli_config.state_dir,
|
|
113
|
+
)
|
|
114
|
+
console = Console()
|
|
115
|
+
reg_path = state_dir / cli_constants.REGISTRY_FILENAME
|
|
116
|
+
reg = GraphRegistry(reg_path).load()
|
|
117
|
+
if reg.get(name) is None:
|
|
118
|
+
console.print(f"[red]Unknown connection[/red] {name!r}")
|
|
119
|
+
raise typer.Exit(1)
|
|
120
|
+
reg.remove(name)
|
|
121
|
+
reg.save()
|
|
122
|
+
console.print(f"[green]Removed[/green] {name!r} from registry.")
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
"""Wire connection-management Typer commands onto the root app."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import typer
|
|
6
|
+
|
|
7
|
+
from pretensor.cli.commands.connections.add_remove import register_add_remove_commands
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def register_connection_commands(app: typer.Typer) -> None:
|
|
11
|
+
"""Attach ``add`` and ``remove`` to ``app``."""
|
|
12
|
+
register_add_remove_commands(app)
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
"""``pretensor export`` command."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import json
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
|
|
8
|
+
import typer
|
|
9
|
+
from rich.console import Console
|
|
10
|
+
|
|
11
|
+
from pretensor.cli import constants as cli_constants
|
|
12
|
+
from pretensor.cli.config_file import get_cli_config, resolve_path_option
|
|
13
|
+
from pretensor.core.portable_export import export_graph_payload
|
|
14
|
+
from pretensor.core.registry import DatabaseNotFoundError, GraphRegistry
|
|
15
|
+
from pretensor.core.store import KuzuStore
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def register_export_command(app: typer.Typer, *, console: Console) -> None:
|
|
19
|
+
@app.command("export")
|
|
20
|
+
def export_command(
|
|
21
|
+
database: str = typer.Option(
|
|
22
|
+
...,
|
|
23
|
+
"--database",
|
|
24
|
+
"-d",
|
|
25
|
+
help="Registry connection name to export (see `pretensor list`).",
|
|
26
|
+
),
|
|
27
|
+
output: Path = typer.Option(
|
|
28
|
+
...,
|
|
29
|
+
"--output",
|
|
30
|
+
"-o",
|
|
31
|
+
help="Target JSON path for portable graph export.",
|
|
32
|
+
file_okay=True,
|
|
33
|
+
dir_okay=False,
|
|
34
|
+
writable=True,
|
|
35
|
+
resolve_path=True,
|
|
36
|
+
),
|
|
37
|
+
state_dir: Path = typer.Option(
|
|
38
|
+
cli_constants.DEFAULT_STATE_DIR,
|
|
39
|
+
"--state-dir",
|
|
40
|
+
help="Directory containing registry.json and graph files.",
|
|
41
|
+
file_okay=False,
|
|
42
|
+
dir_okay=True,
|
|
43
|
+
resolve_path=True,
|
|
44
|
+
),
|
|
45
|
+
ctx: typer.Context = typer.Option(
|
|
46
|
+
None,
|
|
47
|
+
hidden=True,
|
|
48
|
+
),
|
|
49
|
+
) -> None:
|
|
50
|
+
"""Dump one indexed graph to portable pretty-printed JSON."""
|
|
51
|
+
cli_config = get_cli_config(ctx)
|
|
52
|
+
state_dir = resolve_path_option(
|
|
53
|
+
ctx,
|
|
54
|
+
param_name="state_dir",
|
|
55
|
+
cli_value=state_dir,
|
|
56
|
+
config_value=cli_config.state_dir,
|
|
57
|
+
)
|
|
58
|
+
reg_path = state_dir / cli_constants.REGISTRY_FILENAME
|
|
59
|
+
if not reg_path.exists():
|
|
60
|
+
console.print("[red]No registry found.[/red] Run `pretensor index` first.")
|
|
61
|
+
raise typer.Exit(1)
|
|
62
|
+
try:
|
|
63
|
+
reg = GraphRegistry(reg_path).load()
|
|
64
|
+
entry = reg.require(database)
|
|
65
|
+
except DatabaseNotFoundError:
|
|
66
|
+
console.print(
|
|
67
|
+
f"[red]Unknown connection {database!r}.[/red] "
|
|
68
|
+
"Use `pretensor list` to discover available names."
|
|
69
|
+
)
|
|
70
|
+
raise typer.Exit(1)
|
|
71
|
+
except Exception as exc:
|
|
72
|
+
console.print(
|
|
73
|
+
f"[red]Cannot read registry file {reg_path}:[/red] {exc}\n"
|
|
74
|
+
"The registry.json may be corrupt. Delete it and re-run `pretensor index`."
|
|
75
|
+
)
|
|
76
|
+
raise typer.Exit(1) from exc
|
|
77
|
+
graph_path = Path(entry.unified_graph_path or entry.graph_path)
|
|
78
|
+
if not graph_path.exists():
|
|
79
|
+
console.print(
|
|
80
|
+
f"[red]Graph file missing:[/red] {graph_path}\n"
|
|
81
|
+
"Re-run `pretensor index` before exporting."
|
|
82
|
+
)
|
|
83
|
+
raise typer.Exit(1)
|
|
84
|
+
try:
|
|
85
|
+
store = KuzuStore(graph_path)
|
|
86
|
+
except Exception as exc:
|
|
87
|
+
console.print(
|
|
88
|
+
f"[red]Cannot open graph file {graph_path}:[/red] {exc}\n"
|
|
89
|
+
"The file may be corrupt. Re-run `pretensor index`."
|
|
90
|
+
)
|
|
91
|
+
raise typer.Exit(1) from exc
|
|
92
|
+
try:
|
|
93
|
+
payload = export_graph_payload(
|
|
94
|
+
store,
|
|
95
|
+
connection_name=entry.connection_name,
|
|
96
|
+
database_name=entry.database,
|
|
97
|
+
graph_path=graph_path,
|
|
98
|
+
)
|
|
99
|
+
finally:
|
|
100
|
+
store.close()
|
|
101
|
+
try:
|
|
102
|
+
output.parent.mkdir(parents=True, exist_ok=True)
|
|
103
|
+
output.write_text(
|
|
104
|
+
json.dumps(payload, indent=2, ensure_ascii=False) + "\n",
|
|
105
|
+
encoding="utf-8",
|
|
106
|
+
)
|
|
107
|
+
except PermissionError as exc:
|
|
108
|
+
console.print(f"[red]Cannot write export file {output}:[/red] {exc}")
|
|
109
|
+
raise typer.Exit(1) from exc
|
|
110
|
+
except OSError as exc:
|
|
111
|
+
console.print(f"[red]Cannot write export file {output}:[/red] {exc}")
|
|
112
|
+
raise typer.Exit(1) from exc
|
|
113
|
+
stats = payload["stats"]
|
|
114
|
+
console.print(f"[green]Graph exported:[/green] {output}")
|
|
115
|
+
console.print(
|
|
116
|
+
"[dim]"
|
|
117
|
+
f"node_types={stats['node_types']} edge_types={stats['edge_types']} "
|
|
118
|
+
f"nodes={stats['node_count']} edges={stats['edge_count']}"
|
|
119
|
+
"[/dim]"
|
|
120
|
+
)
|