polycodegraph 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.
- codegraph/__init__.py +10 -0
- codegraph/analysis/__init__.py +30 -0
- codegraph/analysis/_common.py +125 -0
- codegraph/analysis/blast_radius.py +63 -0
- codegraph/analysis/cycles.py +79 -0
- codegraph/analysis/dataflow.py +861 -0
- codegraph/analysis/dead_code.py +165 -0
- codegraph/analysis/hotspots.py +68 -0
- codegraph/analysis/infrastructure.py +439 -0
- codegraph/analysis/metrics.py +52 -0
- codegraph/analysis/report.py +222 -0
- codegraph/analysis/roles.py +323 -0
- codegraph/analysis/untested.py +79 -0
- codegraph/cli.py +1506 -0
- codegraph/config.py +64 -0
- codegraph/embed/__init__.py +35 -0
- codegraph/embed/chunker.py +120 -0
- codegraph/embed/embedder.py +113 -0
- codegraph/embed/query.py +181 -0
- codegraph/embed/store.py +360 -0
- codegraph/graph/__init__.py +0 -0
- codegraph/graph/builder.py +212 -0
- codegraph/graph/schema.py +69 -0
- codegraph/graph/store_networkx.py +55 -0
- codegraph/graph/store_sqlite.py +249 -0
- codegraph/mcp_server/__init__.py +6 -0
- codegraph/mcp_server/server.py +933 -0
- codegraph/parsers/__init__.py +0 -0
- codegraph/parsers/base.py +70 -0
- codegraph/parsers/go.py +570 -0
- codegraph/parsers/python.py +1707 -0
- codegraph/parsers/typescript.py +1397 -0
- codegraph/py.typed +0 -0
- codegraph/resolve/__init__.py +4 -0
- codegraph/resolve/calls.py +480 -0
- codegraph/review/__init__.py +31 -0
- codegraph/review/baseline.py +32 -0
- codegraph/review/differ.py +211 -0
- codegraph/review/hook.py +70 -0
- codegraph/review/risk.py +219 -0
- codegraph/review/rules.py +342 -0
- codegraph/viz/__init__.py +17 -0
- codegraph/viz/_style.py +45 -0
- codegraph/viz/dashboard.py +740 -0
- codegraph/viz/diagrams.py +370 -0
- codegraph/viz/explore.py +453 -0
- codegraph/viz/hld.py +683 -0
- codegraph/viz/html.py +115 -0
- codegraph/viz/mermaid.py +111 -0
- codegraph/viz/svg.py +77 -0
- codegraph/web/__init__.py +4 -0
- codegraph/web/server.py +165 -0
- codegraph/web/static/app.css +664 -0
- codegraph/web/static/app.js +919 -0
- codegraph/web/static/index.html +112 -0
- codegraph/web/static/views/architecture.js +1671 -0
- codegraph/web/static/views/graph3d.css +564 -0
- codegraph/web/static/views/graph3d.js +999 -0
- codegraph/web/static/views/graph3d_transform.js +984 -0
- codegraph/workspace/__init__.py +34 -0
- codegraph/workspace/config.py +110 -0
- codegraph/workspace/operations.py +294 -0
- polycodegraph-0.1.0.dist-info/METADATA +687 -0
- polycodegraph-0.1.0.dist-info/RECORD +67 -0
- polycodegraph-0.1.0.dist-info/WHEEL +4 -0
- polycodegraph-0.1.0.dist-info/entry_points.txt +2 -0
- polycodegraph-0.1.0.dist-info/licenses/LICENSE +21 -0
|
@@ -0,0 +1,249 @@
|
|
|
1
|
+
"""SQLite-backed graph store."""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
import json
|
|
5
|
+
import sqlite3
|
|
6
|
+
from collections.abc import Iterable, Iterator
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
from typing import Any
|
|
9
|
+
|
|
10
|
+
from codegraph.graph.schema import Edge, EdgeKind, Node, NodeKind
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class SQLiteGraphStore:
|
|
14
|
+
def __init__(self, path: Path) -> None:
|
|
15
|
+
self._path = path
|
|
16
|
+
path.parent.mkdir(parents=True, exist_ok=True)
|
|
17
|
+
self._con = sqlite3.connect(str(path), check_same_thread=False)
|
|
18
|
+
self._con.execute("PRAGMA foreign_keys = ON")
|
|
19
|
+
self._con.execute("PRAGMA journal_mode = WAL")
|
|
20
|
+
self._init_schema()
|
|
21
|
+
|
|
22
|
+
def _init_schema(self) -> None:
|
|
23
|
+
self._con.executescript(
|
|
24
|
+
"""
|
|
25
|
+
CREATE TABLE IF NOT EXISTS nodes (
|
|
26
|
+
id TEXT PRIMARY KEY,
|
|
27
|
+
kind TEXT NOT NULL,
|
|
28
|
+
name TEXT NOT NULL,
|
|
29
|
+
qualname TEXT NOT NULL,
|
|
30
|
+
file TEXT NOT NULL,
|
|
31
|
+
line_start INTEGER NOT NULL,
|
|
32
|
+
line_end INTEGER NOT NULL,
|
|
33
|
+
signature TEXT,
|
|
34
|
+
docstring TEXT,
|
|
35
|
+
content_hash TEXT,
|
|
36
|
+
language TEXT NOT NULL,
|
|
37
|
+
metadata TEXT NOT NULL DEFAULT '{}'
|
|
38
|
+
);
|
|
39
|
+
CREATE INDEX IF NOT EXISTS idx_nodes_kind ON nodes(kind);
|
|
40
|
+
CREATE INDEX IF NOT EXISTS idx_nodes_file ON nodes(file);
|
|
41
|
+
CREATE TABLE IF NOT EXISTS edges (
|
|
42
|
+
src TEXT NOT NULL,
|
|
43
|
+
dst TEXT NOT NULL,
|
|
44
|
+
kind TEXT NOT NULL,
|
|
45
|
+
file TEXT,
|
|
46
|
+
line INTEGER,
|
|
47
|
+
metadata TEXT NOT NULL DEFAULT '{}',
|
|
48
|
+
PRIMARY KEY (src, dst, kind)
|
|
49
|
+
);
|
|
50
|
+
CREATE INDEX IF NOT EXISTS idx_edges_src ON edges(src);
|
|
51
|
+
CREATE INDEX IF NOT EXISTS idx_edges_dst ON edges(dst);
|
|
52
|
+
CREATE INDEX IF NOT EXISTS idx_edges_kind ON edges(kind);
|
|
53
|
+
CREATE TABLE IF NOT EXISTS meta (
|
|
54
|
+
key TEXT PRIMARY KEY,
|
|
55
|
+
value TEXT NOT NULL
|
|
56
|
+
);
|
|
57
|
+
"""
|
|
58
|
+
)
|
|
59
|
+
self._con.commit()
|
|
60
|
+
|
|
61
|
+
# pragma: codegraph-public-api
|
|
62
|
+
def upsert_node(self, node: Node) -> None:
|
|
63
|
+
self._con.execute(
|
|
64
|
+
"""INSERT OR REPLACE INTO nodes
|
|
65
|
+
(id, kind, name, qualname, file, line_start, line_end,
|
|
66
|
+
signature, docstring, content_hash, language, metadata)
|
|
67
|
+
VALUES (?,?,?,?,?,?,?,?,?,?,?,?)""",
|
|
68
|
+
(
|
|
69
|
+
node.id, node.kind.value, node.name, node.qualname, node.file,
|
|
70
|
+
node.line_start, node.line_end, node.signature, node.docstring,
|
|
71
|
+
node.content_hash, node.language, json.dumps(node.metadata),
|
|
72
|
+
),
|
|
73
|
+
)
|
|
74
|
+
self._con.commit()
|
|
75
|
+
|
|
76
|
+
def upsert_nodes(self, nodes: Iterable[Node]) -> None:
|
|
77
|
+
rows = [
|
|
78
|
+
(
|
|
79
|
+
n.id, n.kind.value, n.name, n.qualname, n.file,
|
|
80
|
+
n.line_start, n.line_end, n.signature, n.docstring,
|
|
81
|
+
n.content_hash, n.language, json.dumps(n.metadata),
|
|
82
|
+
)
|
|
83
|
+
for n in nodes
|
|
84
|
+
]
|
|
85
|
+
with self._con:
|
|
86
|
+
self._con.executemany(
|
|
87
|
+
"""INSERT OR REPLACE INTO nodes
|
|
88
|
+
(id, kind, name, qualname, file, line_start, line_end,
|
|
89
|
+
signature, docstring, content_hash, language, metadata)
|
|
90
|
+
VALUES (?,?,?,?,?,?,?,?,?,?,?,?)""",
|
|
91
|
+
rows,
|
|
92
|
+
)
|
|
93
|
+
|
|
94
|
+
def upsert_edge(self, edge: Edge) -> None:
|
|
95
|
+
self._con.execute(
|
|
96
|
+
"""INSERT OR REPLACE INTO edges (src, dst, kind, file, line, metadata)
|
|
97
|
+
VALUES (?,?,?,?,?,?)""",
|
|
98
|
+
(
|
|
99
|
+
edge.src, edge.dst, edge.kind.value, edge.file, edge.line,
|
|
100
|
+
json.dumps(edge.metadata),
|
|
101
|
+
),
|
|
102
|
+
)
|
|
103
|
+
self._con.commit()
|
|
104
|
+
|
|
105
|
+
def upsert_edges(self, edges: Iterable[Edge]) -> None:
|
|
106
|
+
rows = [
|
|
107
|
+
(e.src, e.dst, e.kind.value, e.file, e.line, json.dumps(e.metadata))
|
|
108
|
+
for e in edges
|
|
109
|
+
]
|
|
110
|
+
with self._con:
|
|
111
|
+
self._con.executemany(
|
|
112
|
+
"""INSERT OR REPLACE INTO edges (src, dst, kind, file, line, metadata)
|
|
113
|
+
VALUES (?,?,?,?,?,?)""",
|
|
114
|
+
rows,
|
|
115
|
+
)
|
|
116
|
+
|
|
117
|
+
def get_node(self, node_id: str) -> Node | None:
|
|
118
|
+
cur = self._con.execute(
|
|
119
|
+
"SELECT id, kind, name, qualname, file, line_start, line_end, "
|
|
120
|
+
"signature, docstring, content_hash, language, metadata "
|
|
121
|
+
"FROM nodes WHERE id=?",
|
|
122
|
+
(node_id,),
|
|
123
|
+
)
|
|
124
|
+
row = cur.fetchone()
|
|
125
|
+
if row is None:
|
|
126
|
+
return None
|
|
127
|
+
return self._row_to_node(row)
|
|
128
|
+
|
|
129
|
+
def iter_nodes(
|
|
130
|
+
self, kind: NodeKind | None = None, file: str | None = None
|
|
131
|
+
) -> Iterator[Node]:
|
|
132
|
+
where: list[str] = []
|
|
133
|
+
params: list[Any] = []
|
|
134
|
+
if kind is not None:
|
|
135
|
+
where.append("kind=?")
|
|
136
|
+
params.append(kind.value)
|
|
137
|
+
if file is not None:
|
|
138
|
+
where.append("file=?")
|
|
139
|
+
params.append(file)
|
|
140
|
+
q = (
|
|
141
|
+
"SELECT id, kind, name, qualname, file, line_start, line_end, "
|
|
142
|
+
"signature, docstring, content_hash, language, metadata FROM nodes"
|
|
143
|
+
)
|
|
144
|
+
if where:
|
|
145
|
+
q += " WHERE " + " AND ".join(where)
|
|
146
|
+
cur = self._con.execute(q, params)
|
|
147
|
+
for row in cur:
|
|
148
|
+
yield self._row_to_node(row)
|
|
149
|
+
|
|
150
|
+
def iter_edges(
|
|
151
|
+
self,
|
|
152
|
+
src: str | None = None,
|
|
153
|
+
dst: str | None = None,
|
|
154
|
+
kind: EdgeKind | None = None,
|
|
155
|
+
) -> Iterator[Edge]:
|
|
156
|
+
where: list[str] = []
|
|
157
|
+
params: list[Any] = []
|
|
158
|
+
if src is not None:
|
|
159
|
+
where.append("src=?")
|
|
160
|
+
params.append(src)
|
|
161
|
+
if dst is not None:
|
|
162
|
+
where.append("dst=?")
|
|
163
|
+
params.append(dst)
|
|
164
|
+
if kind is not None:
|
|
165
|
+
where.append("kind=?")
|
|
166
|
+
params.append(kind.value)
|
|
167
|
+
q = "SELECT src, dst, kind, file, line, metadata FROM edges"
|
|
168
|
+
if where:
|
|
169
|
+
q += " WHERE " + " AND ".join(where)
|
|
170
|
+
cur = self._con.execute(q, params)
|
|
171
|
+
for row in cur:
|
|
172
|
+
yield Edge(
|
|
173
|
+
src=row[0],
|
|
174
|
+
dst=row[1],
|
|
175
|
+
kind=EdgeKind(row[2]),
|
|
176
|
+
file=row[3],
|
|
177
|
+
line=row[4],
|
|
178
|
+
metadata=json.loads(row[5]),
|
|
179
|
+
)
|
|
180
|
+
|
|
181
|
+
def delete_edge(self, src: str, dst: str, kind: EdgeKind) -> None:
|
|
182
|
+
with self._con:
|
|
183
|
+
self._con.execute(
|
|
184
|
+
"DELETE FROM edges WHERE src=? AND dst=? AND kind=?",
|
|
185
|
+
(src, dst, kind.value),
|
|
186
|
+
)
|
|
187
|
+
|
|
188
|
+
def count_unresolved_edges(self) -> int:
|
|
189
|
+
row = self._con.execute(
|
|
190
|
+
"SELECT COUNT(*) FROM edges WHERE dst LIKE 'unresolved::%'"
|
|
191
|
+
).fetchone()
|
|
192
|
+
return int(row[0])
|
|
193
|
+
|
|
194
|
+
def delete_file(self, path: str) -> None:
|
|
195
|
+
cur = self._con.execute("SELECT id FROM nodes WHERE file=?", (path,))
|
|
196
|
+
node_ids = [r[0] for r in cur.fetchall()]
|
|
197
|
+
with self._con:
|
|
198
|
+
for nid in node_ids:
|
|
199
|
+
self._con.execute(
|
|
200
|
+
"DELETE FROM edges WHERE src=? OR dst=?", (nid, nid)
|
|
201
|
+
)
|
|
202
|
+
self._con.execute("DELETE FROM nodes WHERE file=?", (path,))
|
|
203
|
+
with self._con:
|
|
204
|
+
self._con.execute("DELETE FROM edges WHERE file=?", (path,))
|
|
205
|
+
|
|
206
|
+
def count_nodes(self) -> int:
|
|
207
|
+
row = self._con.execute("SELECT COUNT(*) FROM nodes").fetchone()
|
|
208
|
+
return int(row[0])
|
|
209
|
+
|
|
210
|
+
def count_edges(self) -> int:
|
|
211
|
+
row = self._con.execute("SELECT COUNT(*) FROM edges").fetchone()
|
|
212
|
+
return int(row[0])
|
|
213
|
+
|
|
214
|
+
def set_meta(self, key: str, value: str) -> None:
|
|
215
|
+
with self._con:
|
|
216
|
+
self._con.execute(
|
|
217
|
+
"INSERT OR REPLACE INTO meta (key, value) VALUES (?,?)",
|
|
218
|
+
(key, value),
|
|
219
|
+
)
|
|
220
|
+
|
|
221
|
+
def get_meta(self, key: str) -> str | None:
|
|
222
|
+
row = self._con.execute(
|
|
223
|
+
"SELECT value FROM meta WHERE key=?", (key,)
|
|
224
|
+
).fetchone()
|
|
225
|
+
return str(row[0]) if row else None
|
|
226
|
+
|
|
227
|
+
# pragma: codegraph-public-api
|
|
228
|
+
def vacuum(self) -> None:
|
|
229
|
+
self._con.execute("VACUUM")
|
|
230
|
+
|
|
231
|
+
def close(self) -> None:
|
|
232
|
+
self._con.close()
|
|
233
|
+
|
|
234
|
+
@staticmethod
|
|
235
|
+
def _row_to_node(row: tuple[Any, ...]) -> Node:
|
|
236
|
+
return Node(
|
|
237
|
+
id=row[0],
|
|
238
|
+
kind=NodeKind(row[1]),
|
|
239
|
+
name=row[2],
|
|
240
|
+
qualname=row[3],
|
|
241
|
+
file=row[4],
|
|
242
|
+
line_start=row[5],
|
|
243
|
+
line_end=row[6],
|
|
244
|
+
signature=row[7],
|
|
245
|
+
docstring=row[8],
|
|
246
|
+
content_hash=row[9],
|
|
247
|
+
language=row[10],
|
|
248
|
+
metadata=json.loads(row[11]),
|
|
249
|
+
)
|