dbgraph 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 (42) hide show
  1. dbgraph/__init__.py +21 -0
  2. dbgraph/application.py +70 -0
  3. dbgraph/builder/__init__.py +0 -0
  4. dbgraph/builder/graph_builder.py +22 -0
  5. dbgraph/builder/sql_graph_builder.py +245 -0
  6. dbgraph/descriptor/__init__.py +0 -0
  7. dbgraph/descriptor/graph_descriptor.py +68 -0
  8. dbgraph/descriptor/langchain_graph_descriptor.py +74 -0
  9. dbgraph/descriptor/prompt_templates.py +80 -0
  10. dbgraph/entity/__init__.py +0 -0
  11. dbgraph/entity/aspect.py +153 -0
  12. dbgraph/entity/asset.py +26 -0
  13. dbgraph/entity/asset_type.py +13 -0
  14. dbgraph/entity/dbgraph.py +152 -0
  15. dbgraph/entity/link.py +37 -0
  16. dbgraph/entity/link_type.py +13 -0
  17. dbgraph/entity/rdbgraph.py +76 -0
  18. dbgraph/event/__init__.py +0 -0
  19. dbgraph/event/event.py +20 -0
  20. dbgraph/event/event_bus.py +61 -0
  21. dbgraph/event/event_handler.py +11 -0
  22. dbgraph/io/__init__.py +0 -0
  23. dbgraph/io/graph_loader.py +11 -0
  24. dbgraph/io/graph_writer.py +11 -0
  25. dbgraph/io/json_graph_loader.py +160 -0
  26. dbgraph/io/json_graph_writer.py +17 -0
  27. dbgraph/persistent/__init__.py +0 -0
  28. dbgraph/persistent/graph_persistent.py +98 -0
  29. dbgraph/persistent/models.py +222 -0
  30. dbgraph/persistent/sql_graph_persistent.py +388 -0
  31. dbgraph/render/__init__.py +0 -0
  32. dbgraph/render/graph_renderer.py +11 -0
  33. dbgraph/render/markdown_renderer.py +111 -0
  34. dbgraph/render/text_renderer.py +11 -0
  35. dbgraph/search/__init__.py +0 -0
  36. dbgraph/search/bm25_search_engine.py +33 -0
  37. dbgraph/search/search_engine.py +15 -0
  38. dbgraph/utils/__init__.py +0 -0
  39. dbgraph/utils/singleton.py +37 -0
  40. dbgraph-0.1.0.dist-info/METADATA +165 -0
  41. dbgraph-0.1.0.dist-info/RECORD +42 -0
  42. dbgraph-0.1.0.dist-info/WHEEL +4 -0
@@ -0,0 +1,160 @@
1
+ import json
2
+ from dataclasses import dataclass
3
+ from pathlib import Path
4
+
5
+ from dbgraph.entity.aspect import (
6
+ RCategoricalStatistics,
7
+ RColumnSchemaAspect,
8
+ RColumnStatisticsAspect,
9
+ RForeignKeyAspect,
10
+ RNumericalStatistics,
11
+ RTableSchemaAspect,
12
+ RTableStatisticsAspect,
13
+ SemanticAspect,
14
+ )
15
+ from dbgraph.entity.asset import Asset
16
+ from dbgraph.entity.asset_type import AssetType
17
+ from dbgraph.entity.dbgraph import DatabaseGraph
18
+ from dbgraph.entity.link import Link
19
+ from dbgraph.entity.link_type import LinkType
20
+ from dbgraph.io.graph_loader import GraphLoader
21
+
22
+
23
+ @dataclass
24
+ class JSONGraphLoader(GraphLoader):
25
+ json_path: Path
26
+
27
+ def load(self) -> DatabaseGraph:
28
+ with self.json_path.open("r") as f:
29
+ data = json.load(f)
30
+ assets_data = data["assets"]
31
+ links_data = data["links"]
32
+ assets = []
33
+ links = []
34
+ for data in assets_data:
35
+ if data["type"] == "table":
36
+ asset = self._parse_table_asset(data)
37
+ elif data["type"] == "column":
38
+ asset = self._parse_column_asset(data)
39
+ else:
40
+ raise NotImplementedError(f"Invalid asset type '{data['type']}'")
41
+ assets.append(asset)
42
+ for data in links_data:
43
+ if data["type"] == "contain":
44
+ link = self._parse_contain_link(data)
45
+ elif data["type"] == "foreign-key":
46
+ link = self._parse_fk_link(data)
47
+ else:
48
+ raise NotImplementedError(f"Invalid link type '{data['type']}'")
49
+ links.append(link)
50
+ return DatabaseGraph(assets, links)
51
+
52
+ def _parse_fk_link(self, data: dict) -> Link:
53
+ fk_data = data["aspects"]["foreign_key_properties"]
54
+ fk_aspect = RForeignKeyAspect(
55
+ name=fk_data["name"],
56
+ from_column=fk_data["from_column"],
57
+ to_column=fk_data["to_column"],
58
+ on_delete=fk_data["on_delete"],
59
+ on_update=fk_data["on_update"],
60
+ )
61
+ return Link(
62
+ link_id=data["link_id"],
63
+ name=data["name"],
64
+ type=LinkType.FOREIGN_KEY,
65
+ source_id=data["source_id"],
66
+ destination_id=data["destination_id"],
67
+ aspects={"foreign_key_properties": fk_aspect},
68
+ )
69
+
70
+ def _parse_contain_link(self, data: dict) -> Link:
71
+ return Link(
72
+ link_id=data["link_id"],
73
+ name=data["name"],
74
+ type=LinkType.CONTAIN,
75
+ source_id=data["source_id"],
76
+ destination_id=data["destination_id"],
77
+ )
78
+
79
+ def _parse_table_asset(self, data: dict) -> Asset:
80
+ aspects_data = data["aspects"]
81
+ aspects = {}
82
+ if "schema_properties" in aspects_data:
83
+ properties = aspects_data["schema_properties"]
84
+ aspects["schema_properties"] = RTableSchemaAspect(
85
+ name=properties["name"],
86
+ pks=properties["pks"],
87
+ indices=properties["indices"],
88
+ )
89
+ if "statistical_properties" in aspects_data:
90
+ properties = aspects_data["statistical_properties"]
91
+ aspects["statistical_properties"] = RTableStatisticsAspect(
92
+ name=properties["name"],
93
+ num_columns=properties["num_columns"],
94
+ num_rows=properties["num_rows"],
95
+ )
96
+ if "semantic_properties" in aspects_data:
97
+ properties = aspects_data["semantic_properties"]
98
+ aspects["semantic_properties"] = SemanticAspect(
99
+ name=properties["name"],
100
+ description=properties["description"],
101
+ keywords=properties["keywords"],
102
+ )
103
+ return Asset(
104
+ asset_id=data["asset_id"],
105
+ name=data["name"],
106
+ type=AssetType.RTABLE,
107
+ aspects=aspects,
108
+ )
109
+
110
+ def _parse_column_asset(self, data: dict) -> Asset:
111
+ aspects_data = data["aspects"]
112
+ aspects = {}
113
+ if "schema_properties" in aspects_data:
114
+ properties = aspects_data["schema_properties"]
115
+ aspects["schema_properties"] = RColumnSchemaAspect(
116
+ name=properties["name"],
117
+ dtype=properties["dtype"],
118
+ is_pk=properties["is_pk"],
119
+ is_nullable=properties["is_nullable"],
120
+ )
121
+ if "statistical_properties" in aspects_data:
122
+ properties = aspects_data["statistical_properties"]
123
+ numerical_stats = properties["numerical_stats"]
124
+ categorical_stats = properties["categorical_stats"]
125
+ rnumerical_stats = rcategorical_stats = None
126
+ if numerical_stats:
127
+ rnumerical_stats = RNumericalStatistics(
128
+ mean=numerical_stats["mean"],
129
+ std=numerical_stats["std"],
130
+ min=numerical_stats["min"],
131
+ q2=numerical_stats["q2"],
132
+ median=numerical_stats["median"],
133
+ q3=numerical_stats["q3"],
134
+ max=numerical_stats["max"],
135
+ )
136
+ if categorical_stats:
137
+ rcategorical_stats = RCategoricalStatistics(
138
+ value_counts=categorical_stats["value_counts"]
139
+ )
140
+ aspects["statistical_properties"] = RColumnStatisticsAspect(
141
+ name=properties["name"],
142
+ non_null_count=properties["non_null_count"],
143
+ null_count=properties["null_count"],
144
+ numerical_stats=rnumerical_stats,
145
+ categorical_stats=rcategorical_stats,
146
+ )
147
+ if "semantic_properties" in aspects_data:
148
+ properties = aspects_data["semantic_properties"]
149
+ aspects["semantic_properties"] = SemanticAspect(
150
+ name=properties["name"],
151
+ description=properties["description"],
152
+ keywords=properties["keywords"],
153
+ )
154
+
155
+ return Asset(
156
+ asset_id=data["asset_id"],
157
+ name=data["name"],
158
+ type=AssetType.RCOLUMN,
159
+ aspects=aspects,
160
+ )
@@ -0,0 +1,17 @@
1
+ import json
2
+ from dataclasses import asdict, dataclass
3
+ from pathlib import Path
4
+
5
+ from dbgraph.entity.dbgraph import DatabaseGraph
6
+ from dbgraph.io.graph_writer import GraphWriter
7
+
8
+
9
+ @dataclass
10
+ class JSONGraphWriter(GraphWriter):
11
+ json_path: Path
12
+ indent: int | None = None
13
+
14
+ def write(self, graph: DatabaseGraph):
15
+ graph_data = asdict(graph)
16
+ with self.json_path.open("w") as f:
17
+ json.dump(graph_data, f, indent=self.indent)
File without changes
@@ -0,0 +1,98 @@
1
+ from abc import ABC, abstractmethod
2
+
3
+ from dbgraph.entity.aspect import Aspect
4
+ from dbgraph.entity.asset import Asset
5
+ from dbgraph.entity.dbgraph import DatabaseGraph
6
+ from dbgraph.entity.link import Link
7
+
8
+
9
+ class GraphPersistent(ABC):
10
+ """Interface for graph persistent component, which save and load the graphs"""
11
+
12
+ @abstractmethod
13
+ def create_graph(self, graph_id: str, graph_name: str):
14
+ """Insert the graph in the database"""
15
+
16
+ @abstractmethod
17
+ def delete_graph(self, graph_id: str):
18
+ """Delete a graph and return it"""
19
+
20
+ @abstractmethod
21
+ def insert_assets(self, assets: list[Asset], graph_id: str):
22
+ """Insert the assets into database"""
23
+
24
+ @abstractmethod
25
+ def insert_asset(self, asset: Asset, graph_id: str):
26
+ """Insert a single asset"""
27
+
28
+ @abstractmethod
29
+ def insert_links(self, links: list[Link], graph_id: str):
30
+ """Insert links into database"""
31
+
32
+ @abstractmethod
33
+ def insert_link(self, link: Link, graph_id: str):
34
+ """Insert a single link into the database"""
35
+
36
+ @abstractmethod
37
+ def remove_assets(self, assets_ids: list[str], graph_id: str):
38
+ """Remove the assets from the database"""
39
+
40
+ @abstractmethod
41
+ def remove_asset(self, asset_id: str, graph_id: str):
42
+ """Remove the asset from the database"""
43
+
44
+ @abstractmethod
45
+ def remove_links(self, links_ids: list[str], graph_id: str):
46
+ """Remove the links"""
47
+
48
+ @abstractmethod
49
+ def remove_link(self, link_id: str, graph_id: str):
50
+ """Remove the link"""
51
+
52
+ @abstractmethod
53
+ def insert_asset_aspects(self, asset_id: str, aspects: dict[str, Aspect]):
54
+ """Insert aspects into asset"""
55
+
56
+ @abstractmethod
57
+ def get_asset(self, asset_id: str, graph_id: str) -> Asset:
58
+ """Find asset by its ID"""
59
+
60
+ @abstractmethod
61
+ def get_link(self, link_id: str, graph_id: str) -> Link:
62
+ """Find link by its ID"""
63
+
64
+ @abstractmethod
65
+ def insert_link_aspects(self, link_id: str, aspects: dict[str, Aspect]):
66
+ """Insert aspects into link"""
67
+
68
+ @abstractmethod
69
+ def get_aspects_of_asset(self, asset_id: str) -> dict[str, Aspect]:
70
+ """Get the aspects of an asset"""
71
+
72
+ @abstractmethod
73
+ def get_aspects_of_link(self, link_id: str) -> dict[str, Aspect]:
74
+ """Get the aspects of a link"""
75
+
76
+ @abstractmethod
77
+ def get_assets(self, graph_id: str) -> list[Asset]:
78
+ """Return list of assets in a graph"""
79
+
80
+ @abstractmethod
81
+ def get_links(self, graph_id: str) -> list[Link]:
82
+ """Return list of links in a graph"""
83
+
84
+ @abstractmethod
85
+ def load_graph(self, graph_id: str) -> DatabaseGraph:
86
+ """Load the full database graph"""
87
+
88
+ @abstractmethod
89
+ def save_graph(self, graph: DatabaseGraph, graph_id: str):
90
+ """Save the graph into database"""
91
+
92
+ @abstractmethod
93
+ def set_graph_state(self, graph_id: str, completed: bool):
94
+ """Mark the graph as completely built and saved in the database or hasn't been finished building yet"""
95
+
96
+ @abstractmethod
97
+ def get_graph_info(self, graph_id: str) -> tuple[str, str, bool]:
98
+ """Return the graph's info, which is a tuple containing [graph_id, graph_name, completed]"""
@@ -0,0 +1,222 @@
1
+ from sqlalchemy import (
2
+ JSON,
3
+ Boolean,
4
+ ForeignKey,
5
+ String,
6
+ Text,
7
+ )
8
+ from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column, relationship
9
+
10
+
11
+ class Base(DeclarativeBase):
12
+ pass
13
+
14
+
15
+ class GraphModel(Base):
16
+ __tablename__ = "graphs"
17
+
18
+ graph_id: Mapped[str] = mapped_column(
19
+ String(36),
20
+ primary_key=True,
21
+ )
22
+
23
+ name: Mapped[str] = mapped_column(
24
+ Text,
25
+ nullable=False,
26
+ )
27
+
28
+ completed: Mapped[bool] = mapped_column(
29
+ Boolean,
30
+ nullable=False,
31
+ default=False,
32
+ )
33
+
34
+ assets: Mapped[list["AssetModel"]] = relationship(
35
+ back_populates="graph",
36
+ cascade="all, delete-orphan",
37
+ passive_deletes=True,
38
+ )
39
+
40
+ links: Mapped[list["LinkModel"]] = relationship(
41
+ back_populates="graph",
42
+ cascade="all, delete-orphan",
43
+ passive_deletes=True,
44
+ )
45
+
46
+
47
+ class AssetModel(Base):
48
+ __tablename__ = "assets"
49
+
50
+ asset_id: Mapped[str] = mapped_column(
51
+ String(36),
52
+ primary_key=True,
53
+ )
54
+
55
+ name: Mapped[str] = mapped_column(
56
+ Text,
57
+ nullable=False,
58
+ )
59
+
60
+ asset_type: Mapped[str] = mapped_column(
61
+ String(30),
62
+ nullable=False,
63
+ )
64
+
65
+ graph_id: Mapped[str] = mapped_column(
66
+ ForeignKey(
67
+ "graphs.graph_id",
68
+ ondelete="CASCADE",
69
+ ),
70
+ nullable=False,
71
+ index=True,
72
+ )
73
+
74
+ graph: Mapped["GraphModel"] = relationship(
75
+ back_populates="assets",
76
+ )
77
+
78
+ outgoing_links: Mapped[list["LinkModel"]] = relationship(
79
+ foreign_keys="LinkModel.source_id",
80
+ back_populates="source",
81
+ cascade="all, delete-orphan",
82
+ )
83
+
84
+ incoming_links: Mapped[list["LinkModel"]] = relationship(
85
+ foreign_keys="LinkModel.destination_id",
86
+ back_populates="destination",
87
+ cascade="all, delete-orphan",
88
+ )
89
+
90
+ asset_aspects: Mapped[list["AssetAspectModel"]] = relationship(
91
+ back_populates="asset",
92
+ cascade="all, delete-orphan",
93
+ passive_deletes=True,
94
+ )
95
+
96
+
97
+ class LinkModel(Base):
98
+ __tablename__ = "links"
99
+
100
+ link_id: Mapped[str] = mapped_column(
101
+ String(36),
102
+ primary_key=True,
103
+ )
104
+
105
+ name: Mapped[str] = mapped_column(
106
+ Text,
107
+ nullable=False,
108
+ )
109
+
110
+ link_type: Mapped[str] = mapped_column(
111
+ String(30),
112
+ nullable=False,
113
+ )
114
+
115
+ source_id: Mapped[str] = mapped_column(
116
+ ForeignKey(
117
+ "assets.asset_id",
118
+ ondelete="CASCADE",
119
+ ),
120
+ nullable=False,
121
+ index=True,
122
+ )
123
+
124
+ destination_id: Mapped[str] = mapped_column(
125
+ ForeignKey(
126
+ "assets.asset_id",
127
+ ondelete="CASCADE",
128
+ ),
129
+ nullable=False,
130
+ index=True,
131
+ )
132
+
133
+ graph_id: Mapped[str] = mapped_column(
134
+ ForeignKey(
135
+ "graphs.graph_id",
136
+ ondelete="CASCADE",
137
+ ),
138
+ nullable=False,
139
+ index=True,
140
+ )
141
+
142
+ source: Mapped["AssetModel"] = relationship(
143
+ foreign_keys=[source_id],
144
+ back_populates="outgoing_links",
145
+ )
146
+
147
+ destination: Mapped["AssetModel"] = relationship(
148
+ foreign_keys=[destination_id],
149
+ back_populates="incoming_links",
150
+ )
151
+
152
+ graph: Mapped["GraphModel"] = relationship(
153
+ back_populates="links",
154
+ )
155
+
156
+ link_aspects: Mapped[list["LinkAspectModel"]] = relationship(
157
+ back_populates="link",
158
+ cascade="all, delete-orphan",
159
+ passive_deletes=True,
160
+ )
161
+
162
+
163
+ class AssetAspectModel(Base):
164
+ __tablename__ = "asset_aspects"
165
+
166
+ asset_id: Mapped[str] = mapped_column(
167
+ ForeignKey(
168
+ "assets.asset_id",
169
+ ondelete="CASCADE",
170
+ ),
171
+ primary_key=True,
172
+ )
173
+
174
+ name: Mapped[str] = mapped_column(
175
+ String(100),
176
+ primary_key=True,
177
+ )
178
+
179
+ type: Mapped[str] = mapped_column(
180
+ String(30),
181
+ nullable=False,
182
+ )
183
+
184
+ json_aspect: Mapped[dict] = mapped_column(
185
+ JSON,
186
+ nullable=False,
187
+ )
188
+
189
+ asset: Mapped["AssetModel"] = relationship(
190
+ back_populates="asset_aspects",
191
+ )
192
+
193
+
194
+ class LinkAspectModel(Base):
195
+ __tablename__ = "link_aspects"
196
+
197
+ link_id: Mapped[str] = mapped_column(
198
+ ForeignKey(
199
+ "links.link_id",
200
+ ondelete="CASCADE",
201
+ ),
202
+ primary_key=True,
203
+ )
204
+
205
+ name: Mapped[str] = mapped_column(
206
+ String(100),
207
+ primary_key=True,
208
+ )
209
+
210
+ type: Mapped[str] = mapped_column(
211
+ String(30),
212
+ nullable=False,
213
+ )
214
+
215
+ json_aspect: Mapped[dict] = mapped_column(
216
+ JSON,
217
+ nullable=False,
218
+ )
219
+
220
+ link: Mapped["LinkModel"] = relationship(
221
+ back_populates="link_aspects",
222
+ )