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.
- dbgraph/__init__.py +21 -0
- dbgraph/application.py +70 -0
- dbgraph/builder/__init__.py +0 -0
- dbgraph/builder/graph_builder.py +22 -0
- dbgraph/builder/sql_graph_builder.py +245 -0
- dbgraph/descriptor/__init__.py +0 -0
- dbgraph/descriptor/graph_descriptor.py +68 -0
- dbgraph/descriptor/langchain_graph_descriptor.py +74 -0
- dbgraph/descriptor/prompt_templates.py +80 -0
- dbgraph/entity/__init__.py +0 -0
- dbgraph/entity/aspect.py +153 -0
- dbgraph/entity/asset.py +26 -0
- dbgraph/entity/asset_type.py +13 -0
- dbgraph/entity/dbgraph.py +152 -0
- dbgraph/entity/link.py +37 -0
- dbgraph/entity/link_type.py +13 -0
- dbgraph/entity/rdbgraph.py +76 -0
- dbgraph/event/__init__.py +0 -0
- dbgraph/event/event.py +20 -0
- dbgraph/event/event_bus.py +61 -0
- dbgraph/event/event_handler.py +11 -0
- dbgraph/io/__init__.py +0 -0
- dbgraph/io/graph_loader.py +11 -0
- dbgraph/io/graph_writer.py +11 -0
- dbgraph/io/json_graph_loader.py +160 -0
- dbgraph/io/json_graph_writer.py +17 -0
- dbgraph/persistent/__init__.py +0 -0
- dbgraph/persistent/graph_persistent.py +98 -0
- dbgraph/persistent/models.py +222 -0
- dbgraph/persistent/sql_graph_persistent.py +388 -0
- dbgraph/render/__init__.py +0 -0
- dbgraph/render/graph_renderer.py +11 -0
- dbgraph/render/markdown_renderer.py +111 -0
- dbgraph/render/text_renderer.py +11 -0
- dbgraph/search/__init__.py +0 -0
- dbgraph/search/bm25_search_engine.py +33 -0
- dbgraph/search/search_engine.py +15 -0
- dbgraph/utils/__init__.py +0 -0
- dbgraph/utils/singleton.py +37 -0
- dbgraph-0.1.0.dist-info/METADATA +165 -0
- dbgraph-0.1.0.dist-info/RECORD +42 -0
- 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
|
+
)
|